#lang racket
(require gigls/unsafe)
;;; Daniel Delay
;;; David Kraemer
;;; Steve Yang

;;; Literature made use of in this project
;;;   Scheme's complex number functions reference:
;;;     http://docs.racket-lang.org/reference/generic-numbers.html
;;;   'Mandelbrot set', Wikipedia:
;;;     http://en.wikipedia.org/wiki/Mandelbrot_set
;;;   'Cardioid', Wikipedia:
;;;     http://en.wikipedia.org/wiki/Cardioid
;;;   'Reading: building images by iterating over positions', Weinman et al
;;;     http://www.cs.grinnell.edu/~weinman/courses/CSC151/2014S/readings/iterate-positions-reading.html
;;;   'Reading: anonymous procedures', Weinman et al
;;;     http://www.cs.grinnell.edu/~weinman/courses/CSC151/2014S/readings/anonymous-procedures-reading.html
;;;   'Reading: naming values with local bindings', Weinman et al
;;;     http://www.cs.grinnell.edu/~weinman/courses/CSC151/2014S/readings/let-reading.html

;;; Procedure:
;;;   gimp->cartesian-x
;;; Parameters:
;;;   x, an integer
;;;   image-width, a positve integer
;;; Purpose:
;;;   converts a pixel's column in Gimp to cartesian plane x-coordinate
;;; Produces:
;;;   cartesian-x, a number 
;;; Preconditions:
;;;   x must be nonnegative
;;; Postconditions: 
;;;   (and (>= cartesian-x -2) (<= cartesian-x 2))
(define gimp->cartesian-x
  (λ (x image-width)
    (* (/ 4 image-width) (- x (* image-width 0.5)))))

;;; Procedure:
;;;   gimp->cartesian-y
;;; Parameters:
;;;   y, a positive integer
;;;   image-width, a positive integer
;;; Purpose:
;;;   converts a pixel's row in Gimp to cartesian plane y-coordinate
;;; Produces:
;;;   cartesian-y, a number 
;;; Preconditions:
;;;   y must be nonnegative
;;; Postconditions: 
;;;   (and (>= cartesian-y -2) (<= cartesian-y 2))
(define gimp->cartesian-y
  (λ (y image-height)
    (* (/ 4 image-height) (- (* image-height 0.5) y))))

;;; Procedure:
;;;   z
;;; Parameters:
;;;   x, a real number
;;;   y, a real number	
;;;   width, a positive integer
;;;   height, a positive integer
;;; Purpose:
;;;   converts pixels on an image to complex numbers
;;; Produces:
;;;   complex-z, a number
;;; Preconditions:
;;;   [no additional]
;;; Postconditions: 
;;;   (and (>= (real-part complex-z) -2) (<= (real-part complex-z) 2) (>= (imag-part complex-z) -2) (<= (imag-part complex-z) 2))
(define z
  (λ (x y width height)
    (let ([i (expt -1 1/2)])
    (+ (gimp->cartesian-x x width) (* (gimp->cartesian-y y height) i)))))

;;; Procedure:
;;;   func-power
;;; Parameters:
;;;   func, a function
;;;   x, a number
;;;   n, an integer
;;; Purpose:
;;;   applies the function func onto the value x, n times
;;; Produces:
;;;   func-on-x, a number
;;; Preconditions:
;;;   func must take one argument
;;; Postconditions: 
;;;   (= (func-power func x n) (func (func (func ... (func x)))),
;;;   where func is applied n times
(define func-power
  (λ (func x n)
    (letrec ([ kernel 
               (λ (func fx m)
               (if (<= m 1)
                   (func fx)
                   (kernel func (func fx) (- m 1))))])
      (kernel func x n))))

;;; Procedure:
;;;   func
;;; Parameters:
;;;   x, a number
;;;   N, an integer
;;; Purpose:
;;;   calculates z =x^2 + c, where c is determined by N
;;; Produces:
;;;   z, a complex number
;;; Preconditions:
;;;   (and (>= N 0) (<= N 999))
;;; Postconditions: 
;;;   (= z (+ (expt x 2) (complex-constant N)))
(define func
  (λ (x N)
    (+ (expt x 2) (complex-constant N))))

;;; Procedure:
;;;   complex-constant
;;; Parameters:
;;;   N, an integer
;;; Purpose:
;;;   produces a unique complex number for each N
;;; Produces:
;;;   c, a complex number
;;; Preconditions:
;;;   (and (>= N 0) (<= N 999))
;;; Postconditions: 
;;;   if (= N (/(* t 500) pi)), then
;;;   (= (complex-constant N) (/ (- 1 (expt (- (expt e (* i t)) 1) 2)) 4),
;;;   where (and (= e 2.71828) (= i (expt -1 1/2)))
(define complex-constant
  (λ (N)
    (let ([ t (/ (* N pi) 500)]
          [ i (expt -1 1/2)]
          [ e 2.71828])
     (/ (- 1 (expt (- (expt e (* i t)) 1) 2)) 3.6))))

;;; Procedure:
;;;   nth-test
;;; Parameters:
;;;   z, a complex number
;;;   k, a positive integer
;;;   n, a positive integer
;;;   N, a positive integer
;;; Purpose:
;;;   determines how many applications of a function onto z to 'escape' k
;;; Produces:
;;;   number-of-applications, an integer
;;; Preconditions:
;;;   (and (>= N 0) (<= N 999))
;;; Postconditions: 
;;;   if for all integers x such that (and (>= x 0) (<= x n)) and
;;;   |f x(z)|<k, then (= (nth-test z k n N) 0)
;;;   otherwise,
;;;   (and (>= (nth-test z k n N) 1) (<= (nth-test z k n N) n))
(define nth-test 
  (λ (z k n N)
    (letrec ([kernel
              (λ (z k m l)
                (let ([val (func-power (λ (x) (func x N)) z m)]
                      [len (magnitude (func-power (λ (x) (func x N)) z m))])
                (cond
                  [(< m l)
                   (if (> len k)
                       m
                       (kernel z k (+ m 1) l))]
                  [else
                   0])))])
      (kernel z k 1 n))))

;;; Procedure:
;;;   colors
;;; Parameters:
;;;   N, an integer
;;;   depth, an integer 
;;; Purpose:
;;;   creates a list of 'depth' number of colors on a scale determined by N mod 10
;;; Produces:
;;;   lst, a list of colors
;;; Preconditions:
;;;   (and (>= N 0) (<= N 999))
;;;   (>= depth 0)
;;; Postconditions: 
;;;   (equal? (list-length lst) depth)
;;;   if N mod 10 = 0, then lst is black-to-red colors
;;;   if N mod 10 = 1, then lst is black-to-green colors
;;;   if N mod 10 = 2, then lst is black-to-blue colors
;;;   if N mod 10 = 3, then lst is black-to-cyan colors
;;;   if N mod 10 = 4, then lst is black-to-purple colors
;;;   if N mod 10 = 5, then lst is black-to-yellow colors
;;;   if N mod 10 = 6, then lst is white-to-red colors
;;;   if N mod 10 = 7, then lst is white-to-blue colors
;;;   if N mod 10 = 8, then lst is white-to-green colors
;;;   if N mod 10 = 9, then lst is white-to-pink colors
(define colors
  (λ (N depth)
    (cond [(equal? (mod N 10) 0)
           (map (λ (x) (irgb (+ (- 0 x) (* 1.8 x (/ 256 depth)))
                             0
                             0)) (iota (+ depth 1)))]
          [(equal? (mod N 10) 1)
           (map (λ (x) (irgb 0
                             (+ (- 0 x) (* 1.8 x (/ 256 depth)))
                             0)) (iota (+ depth 1)))]
          [(equal? (mod N 10) 2)
           (map (λ (x) (irgb 0
                             0
                             (+ (- 0 x) (* 1.8 x (/ 256 depth))))) (iota (+ depth 1)))]
          [(equal? (mod N 10) 3)
           (map (λ (x) (irgb 0
                             (+ (- 0 x) (* 1.8 x (/ 256 depth)))
                             (+ (- 0 x) (* 1.8 x (/ 256 depth))))) (iota (+ depth 1)))]
          [(equal? (mod N 10) 4)
           (map (λ (x) (irgb (+ (- 0 x) (* 1.8 x (/ 256 depth)))
                             0
                             (+ (- 0 x) (* 1.8 x (/ 256 depth))))) (iota (+ depth 1)))]
          [(equal? (mod N 10) 5)
           (map (λ (x) (irgb (+ (- 0 x) (* 1.8 x (/ 256 depth)))
                             (+ (- 0 x) (* 1.8 x (/ 256 depth)))
                             0)) (iota (+ depth 1)))]
          [(equal? (mod N 10) 6)
           (map (λ (x) (irgb 255
                             (- (+ 255 x) (* x 1.6  (/ 256 depth)))
                             (- (+ 255 x) (* x 1.6  (/ 256 depth))))) (iota (+ depth 1)))]
          [(equal? (mod N 10) 7)
           (map (λ (x) (irgb(- (+ 255 x) (* x 1.6  (/ 256 depth))) 
                            255                  
                            (- (+ 255 x) (* x 1.6  (/ 256 depth))))) (iota (+ depth 1)))]
          [(equal? (mod N 10) 8)
           (map (λ (x) (irgb (- (+ 255 x) (* x 1.6  (/ 256 depth)))
                             (- (+ 255 x) (* x 1.6  (/ 256 depth))) 
                             255)) (iota (+ depth 1)))]
          [(equal? (mod N 10) 9)
           (map (λ (x) (irgb 255
                             (- (+ 255 x) (* x 1.6 (/ 256 depth)))
                             255)) (iota (+ depth 1)))])))
;;; Procedure:
;;;   image-series
;;; Parameters:
;;;   N, an integer
;;;   width, a positive integer
;;;   height, a positive integer
;;; Purpose:
;;;   creates a unique Julia set determined by N
;;; Produces:
;;;   image-series, an image
;;;Preconditions:
;;;   (and (>= N 0) (<= N 999))
;;;Postconditions: 
;;;   (= (image-height (image-series)) height)
;;;   (= (image-width (image-series)) width)
(define image-series
  (λ (N width height)
    (image-show (image-compute (λ (col row) (list-ref (colors N 100) 
                                          (nth-test (z col row width height) 50000 100 N)))
                   width height))))