#lang racket
(require gigls/unsafe)

;;; 356, 545, 566 and of course 1111111

;;; Defines the basic colors and variables
(define shine-color (rgb-new 255 255 255))
(define dark-color (rgb-new 0 0 0))
(define bubble-to-size-ratio 10)

;;; Main function, really a helper function to bubbles-helper
;;; that also sets up the layers and 
(define bubbles
  (lambda (final-width final-height n)
    ;;; Establishes the initial colors
    (define background-color (rgb-new 0 0 (+ (mod n 20) 160)))
    (define background-color-gradient (rgb-new (+ (mod n 10) 59) (+ (mod n 20) 160) 255))
    (context-set-fgcolor! background-color-gradient)
    (context-set-bgcolor! background-color)
    ;;; Makes a canvas
    (define width 500)
    (define height 500)
    (define image (image-new width height))
    ;;; Defines and establishes all of the layers
    (define gradient-layer (gimp-layer-new image
                                           width height 0
                                           "Gradient Layer"
                                           100 0))
    (define shine-layer (gimp-layer-new image
                                        width height 0
                                        "Shine Layer"
                                        100 0))
    (define bubble-layer (gimp-layer-new image
                                         width height 0
                                         "Bubble Layer"
                                         100 0))
    (define crescent-layer (gimp-layer-new image
                                           width height 0
                                           "Crescent Layer"
                                           100 0))
    (setup-layer image width height bubble-layer)
    (setup-layer image width height crescent-layer)
    (setup-layer image width height shine-layer)
    (setup-layer image width height gradient-layer)
    
    ;;; Defines the light source coordinates
    (define col-light-source (* (mod n 21) (/ width 20)))
    (define row-light-source (- 0 height))
    ;;; Colors the background
    (gimp-edit-blend (- (car gradient-layer) 1) 0 0 0
                     100 0 0 0 0 1 0 0 0 0 0 height)
    (image-show image)
    ;;; Displays the bubbles
    (bubbles-helper image width height
                    col-light-source
                    row-light-source
                    gradient-layer shine-layer bubble-layer crescent-layer
                    (+ 5 (mod n 56))
                    n)
    ;;; Puts a light gradient out
    (image-select-nothing! image)
    (context-set-fgcolor! background-color-gradient)
    ;;; Scales and updates the image
    (flatten-and-scale image final-width final-height)
    (context-update-displays!)
    ))

;;; Helper test function to display many many bubbles
(define bubbles-helper
  (lambda (image width height col-light-source row-light-source gradient-layer shine-layer bubble-layer crescent-layer times n)
    (let* ([max-size (round (/ (max width height) bubble-to-size-ratio))]
           [min-size (round (/ (/ (max width height) bubble-to-size-ratio) 2))]
           [blur-constant (round (/ (/ (+ width height) 2) 50))]
           [current-col (pseudo-random n times width)]
           [current-row (pseudo-random current-col n height)]
           [current-size (+ min-size 
                            (pseudo-random current-col current-row max-size))]
           [ray-end (pseudo-random current-row current-size width)]
           [ray-depth (+ (* height 0.4)
                         (pseudo-random current-size ray-end (* height 0.8)))]
           [ray-width (+ (* width 0.05)
                         (pseudo-random ray-depth current-col (* width 0.15)))]
           [ray-color (rgb-new (+ 186 (mod times 21)) (+ 217 (mod times 21)) 255)])
      ;;; Begins showing bubbles and rays
      (cond ((= times 0)
             image)
            (else
             (bubbles-helper image
                             width
                             height
                             col-light-source
                             row-light-source
                             gradient-layer
                             shine-layer
                             bubble-layer
                             crescent-layer
                             (- times 1)
                             n)
             (make-bubble image
                          current-row
                          current-col
                          col-light-source
                          row-light-source
                          current-size
                          width
                          height
                          blur-constant
                          gradient-layer
                          shine-layer
                          bubble-layer
                          crescent-layer)
             ;;; Have to have an easter egg somewhere
             ;;; Also this could have been coded a lot better.
             (if (= n 1111111)
                 (make-ray image 
                           blur-constant
                           col-light-source
                           row-light-source
                           ray-end
                           ray-depth
                           ray-width
                           (rgb-new (+ (pseudo-random ray-color ray-width 205) 150)
                                    (+ (pseudo-random current-row current-col 205) 150)
                                    (+ (pseudo-random current-size ray-end 205) 150))
                           gradient-layer)
                 (make-ray image 
                           blur-constant
                           col-light-source
                           row-light-source
                           ray-end
                           ray-depth
                           ray-width
                           ray-color
                           gradient-layer)))))))

;;; Runs all the procedures required to create a bubble
(define make-bubble
  (lambda (image col row col-light-source row-light-source diameter width height
                 blur-constant gradient-layer shine-layer bubble-layer crescent-layer)
    (let* ([outline-opacity 50]
           [theta (- (/ pi 2) (atan (/ (- col-light-source col) (- row row-light-source))))]
           [radius (/ diameter 2)]
           [inner-radius (* 0.2525 diameter)]
           [distance (sqrt (* inner-radius inner-radius 2))]
           [outline-diameter-mod 10]
           [outline-width (ceiling (/ distance outline-diameter-mod))]
           [outline-proportion (- 1 (/ 0.25 outline-diameter-mod))]
           [outline-diameter (* outline-proportion diameter)]
           [shine-radius (/ (/ diameter 10) 2)]
           [theta-mod (/ shine-radius (* 2 distance))]
           [shine 50])
      (make-gradient image row col diameter theta radius shine-color gradient-layer)
      (bubble-outline image col row outline-diameter outline-width outline-opacity blur-constant bubble-layer)
      (make-blur image col row outline-diameter blur-constant gradient-layer shine-layer bubble-layer crescent-layer)
      (make-crescent image col row diameter theta shine crescent-layer)
      (make-shine image col row distance diameter theta theta-mod shine shine-radius shine-layer bubble-layer)
      (context-update-displays!)
      )))

;;; Raises the layer and makes the soft gradient change
;;;  for the bubble
(define make-gradient
  (lambda (image row col diameter theta radius shine-color gradient-layer)
    (gimp-image-set-active-layer image (car gradient-layer))
    (context-set-brush! "2. Hardness 075" (* 4 radius))
    (image-select-ellipse! image
                           REPLACE
                           (ceiling (- col (/ diameter 2.0)))
                           (ceiling (- row (/ diameter 2.0)))
                           diameter diameter)
    ;;; Lighter, higher gradient
    (context-set-fgcolor! shine-color)
    (image-blot! image
                 (find-pos-col col (* 1.75 radius) theta)
                 (find-pos-row row (* 1.75 radius) theta))
    ;;; Darker, lower gradient
    (context-set-fgcolor! dark-color)
    (gimp-context-set-opacity 25)
    (image-blot! image
                 (find-pos-col col (* 1.75 radius) (+ theta pi))
                 (find-pos-row row (* 1.75 radius) (+ theta pi)))))

;;; Creates a crescent shape
;;;   I'd like to claim that I used incredibly complex
;;;   mathematics in order to calculate the values needed
;;;   in this procedure, but in reality I just did guess and check.
(define make-crescent
  (lambda (image col row diameter theta shine crescent-layer)
    (let ([inner-diameter (* 0.85 diameter)])
      (gimp-image-set-active-layer image (car crescent-layer))
      (image-select-ellipse! image
                             REPLACE
                             (ceiling (- col (/ diameter 2.0)))
                             (ceiling (- row (/ diameter 2.0)))
                             diameter diameter)
      (context-set-fgcolor! shine-color)
      (gimp-context-set-opacity (* 0.50 shine))
      (context-set-brush! "2. Hardness 100" (* diameter 0.70))
      ;;; Creates a smaller sphere off centered
      (image-blot! image
                   (find-pos-col col (* diameter 0.115) (+ pi theta))
                   (find-pos-row row (* diameter 0.115) (+ pi theta)))
      ;;; Clears a larger area creating a crescent shape.
      ;;; This is why the crescents do not show behind the bubbles
      (image-select-ellipse! image
                             REPLACE
                             (ceiling (- col (/ inner-diameter 2.0)))
                             (ceiling (- row (/ inner-diameter 2.0)))
                             inner-diameter
                             inner-diameter)
      (gimp-edit-clear (car crescent-layer))
      )))

;;; Draws the outer edge of the bubble on the bottom layer
;;; The numbers need tinkering with.
(define bubble-outline
  (lambda (image col row outline-diameter outline-width outline-opacity blur-constant bubble-layer)
    (gimp-image-set-active-layer image (car bubble-layer))
    (context-set-fgcolor! shine-color)
    (gimp-context-set-opacity outline-opacity)
    (image-select-ellipse! image
                           REPLACE
                           (ceiling (- col (/ outline-diameter 2)))
                           (ceiling (- row (/ outline-diameter 2)))
                           (ceiling outline-diameter)
                           (ceiling outline-diameter))
    (context-set-brush! "2. Hardness 025" outline-width)
    (image-stroke-selection! image)))

;;; Blurs the bubble over almost all layers
(define make-blur
  (lambda (image col row outline-diameter blur-constant gradient-layer shine-layer bubble-layer crescent-layer)
    (let ([bubble-blur (/ blur-constant 10)]
          [shine-blur (/ blur-constant 2.5)]
          [crescent-blur (/ blur-constant 5)])
      (image-select-ellipse! image
                             REPLACE
                             (ceiling (- col (/ outline-diameter 2)))
                             (ceiling (- row (/ outline-diameter 2)))
                             (ceiling outline-diameter)
                             (ceiling outline-diameter))
      (plug-in-gauss 1 image
                     (car bubble-layer)
                     bubble-blur
                     bubble-blur
                     0)
      (plug-in-gauss 1 image
                     (car gradient-layer)
                     bubble-blur
                     bubble-blur
                     0)
      (plug-in-gauss 1 image
                     (car crescent-layer)
                     crescent-blur
                     crescent-blur
                     0)
      (plug-in-gauss 1 image
                     (car shine-layer)
                     shine-blur
                     shine-blur
                     0))))

;;; Draws the 'shine' part of the bubble. The little
;;;  rounded semi-circle thing that makes it look shiny
(define make-shine
  (lambda (image col row distance diameter theta theta-mod shine shine-radius shine-layer bubble-layer)
    (gimp-image-set-active-layer image (car shine-layer))
    (gimp-context-set-opacity shine)
    (make-shiny-thing image col row distance theta theta-mod
                      (* 2 shine-radius) (* 2 shine-radius) 4)
    (gimp-image-set-active-layer image (car bubble-layer))))

;;; Makes a shiny thing multiple times
;;; where it iterates 2n+1 times where n is times
(define make-shiny-thing
  (lambda (image col row distance theta theta-mod initial-diameter diameter times)
    (let ([current-mod (* times theta-mod)])
      (context-set-brush! "2. Hardness 075" diameter)
      (cond ((= times 0)
             (image-blot! image 
                          (find-pos-col col distance theta)
                          (find-pos-row row distance theta)))
            (else
             (image-blot! image 
                          (find-pos-col col distance (- theta current-mod))
                          (find-pos-row row distance (- theta current-mod)))
             (image-blot! image 
                          (find-pos-col col distance (+ theta current-mod))
                          (find-pos-row row distance (+ theta current-mod)))
             (make-shiny-thing image col row distance theta theta-mod
                               initial-diameter
                               (+ (* 0.02 initial-diameter) diameter) 
                               (- times 1)))))))

;;; These two procedures find their positions
;;; respective to the origin
(define find-pos-col
  (lambda (col distance angle)
    (+ col (* distance (cos angle)))))
(define find-pos-row
  (lambda (row distance angle)
    (- row (* distance (sin angle)))))

;;; Sets up the values for a layer along with
;;; the transparency settings.
(define setup-layer
  (lambda (image width height layer-name)
    (gimp-image-add-layer image (car layer-name) 1)
    (gimp-image-raise-layer image (car layer-name))
    (gimp-layer-add-alpha (car layer-name))
    (gimp-drawable-fill (car layer-name) 3)))

;;; Creates rays of light
;;; A triangle is formed using a starting point, and two ending points,
;;; both of which branch of off of the starting.
(define make-ray
  (lambda (image blur-constant start-col start-row end-col end-row ray-width color gradient-layer)
    (gimp-context-set-opacity 25)
    (context-set-fgcolor! color)
    (gimp-image-select-polygon image 2 6 (vector start-col
                                                 start-row
                                                 (- end-col (/ ray-width 2))
                                                 end-row
                                                 (+ end-col (/ ray-width 2))
                                                 end-row))
    ;;; Blends from the starting point of the ray to the specified row,
    ;;; remaining in the same column.
    (gimp-edit-blend (- (car gradient-layer) 1) 2 0 2
                     100 0 0 0 0 1 0 0 start-col start-row start-col end-row)
    (plug-in-gauss 1 image
                   (- (car gradient-layer) 1)
                   blur-constant
                   blur-constant
                   0)))

;;; Something of a random generator. It can take in
;;; multiple values in order to generate a more 'random' number.
;;; It has an upper limit as well.
(define pseudo-random
  (lambda (val1 val2 limit)
    (round (mod (round (* (/ (+ val1 val2) 2) (+ (+ (mod val1 2)
                                                    (mod val2 3))
                                                 (+ (- (mod val1 7)
                                                       (mod val2 5))
                                                    (* (mod val1 11)
                                                       (mod val2 13)))
                                                 (- (mod val1 17)
                                                    (mod val2 19))
                                                 14))) limit))))

;;; I feel like such a cheater doing this.
;;; This procedure takes all the layers and merges them into
;;; a single one. It then scales the layer to the correct dimensions
;;; and fits the canvas to it.
(define flatten-and-scale
  (lambda (image final-width final-height)
    (gimp-layer-scale (car (gimp-image-merge-visible-layers image 0))
                      final-width
                      final-height
                      0)
    (gimp-image-resize-to-layers image)))





