ChristmasTree <- function(recipe) {

# Config
author <- recipe$Description$Title$author
title <- recipe$Description$Title$text
title.col <- recipe$Description$Title$color
bg.col <- strsplit(recipe$background$color, ' ')[[1]]
tree.col <- strsplit(recipe$tree$leaves$color, ' ')[[1]]
trunk.col <- strsplit(recipe$tree$trunk$color, ' ')[[1]]
ball.col <- strsplit(recipe$balls$color, ' ')[[1]]
snow.col <- strsplit(recipe$snow$color, ' ')[[1]]
snow.level <- recipe$snow$level
star <- recipe$star$shape
fileout <- recipe$fileout

# Sanity checks
if (length(title.col) != 1) stop("Title can only have one color.")
if (length(tree.col) != 1) stop("Tree leaves can only have one color.")
if (length(trunk.col) != 1) stop("Tree trunk can only have one color.")
if (length(bg.col) != 1) stop("Background can only have one color.")
if (length(star) != 1) stop("Star can only have one shape.")
if (!star %in% c('star', 'heart', 'poop', 'smile', 'smirk')) stop("Star can only be star, heart, poop, smile, or smirk.")

if (!is.null(fileout)) {
  png(fileout, width = 480, height = 480)
} else {
  dev.new(width = 6.5, height = 6.5)
}

ll <- 252

par(bg = bg.col)
plot(0, xlim = c(-0.75, 0.75), axes = F, xlab = "", ylab = "", ylim = c(-50, ll + 48))

# trunk
rect(-0.1, -50, 0.08, 1, col = trunk.col, border = trunk.col)

# star glow
if (star != 'heart') {
  col_star <- rgb(1, 1, 0, 0.05)
} else {
  col_star <- rgb(1, 0.6, 0.8, 0.1)
}
for(i in 12:1) {
  points(-0.02, ll + 1, pch = 19, col = col_star, cex = 5 + (0.65 * i))
}

# Tree
s <- (ll:1)/1000
a <- 0
i_seq <- rep(exp(-seq(1, 125, by = 2) * 0.016), 4) * s + s

if (tree.col == 'green') {
  for (i in i_seq) {
    a <- a + 1
    j_seq <- seq(-i, i, 0.1)
    for (j in j_seq) {
      points(j, a, col = rgb(0, i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2, 0),
             pch = 8, cex = 2.8, lwd = 1.5)
    }
  }
} else if (tree.col == 'blue') {
  for (i in i_seq) {
    a <- a + 1
    j_seq <- seq(-i, i, 0.1)
    for (j in j_seq) {
      points(j, a, col = rgb(0, 0, i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2),
             pch = 8, cex = 2.8, lwd = 1.5)
    }
  }
} else if (tree.col == 'red') {
  for (i in i_seq) {
    a <- a + 1
    j_seq <- seq(-i, i, 0.1)
    for (j in j_seq) {
      points(j, a, col = rgb(i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2, 0, 0),
             pch = 8, cex = 2.8, lwd = 1.5)
    }
  }
} else if (tree.col == 'yellow') {
  for (i in i_seq) {
    a <- a + 1
    j_seq <- seq(-i, i, 0.1)
    for (j in j_seq) {
      points(j, a, col = rgb(i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2, i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2, 0),
             pch = 8, cex = 2.8, lwd = 1.5)
    }
  }
} else if (tree.col %in% c('gray', 'grey')) {
  for (i in i_seq) {
    a <- a + 1
    j_seq <- seq(-i, i, 0.1)
    for (j in j_seq) {
      points(j, a, col = rgb(i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2, i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2, i * 0.7 + 0.2 * runif(1, 0.6, 1) - 0.5 * j^2),
             pch = 8, cex = 2.8, lwd = 1.5)
    }
  }
} else {
  stop("Tree leaf color is not supported.")
}

# Christmas bulbs
cols1 <- sample(c(rgb(1, 0, 0, 0.1), rgb(0, 1, 0, 0.1), rgb(1, 1, 0, 0.1), rgb(0, 0.2, 1, 0.1)))
cols2 <- sample(c(rgb(1, 0, 0, 0.1), rgb(0, 1, 0, 0.1), rgb(1, 1, 0, 0.1), rgb(0, 0.2, 1, 0.1)))
cols3 <- sample(c(rgb(1, 0, 0, 0.1), rgb(0, 1, 0, 0.1), rgb(1, 1, 0, 0.1), rgb(0, 0.2, 1, 0.1)))
cols4 <- sample(c(rgb(1, 0, 0, 0.1), rgb(0, 1, 0, 0.1), rgb(1, 1, 0, 0.1), rgb(0, 0.2, 1, 0.1)))

## small bulbs
for (i in 11:1) {
  points(seq(-0.5, 0.5, 0.1), rep(0, 11), pch = 19, cex = 0.15 + (0.12 * i), col = cols1)
  points(seq(-0.375, 0.375, 0.1), rep(64.5, 8), pch = 19, cex = 0.15 + (0.12 * i), col = cols2)
  points(seq(-0.25, 0.25, 0.1), rep(125, 6), pch = 19, cex = 0.15 + (0.12 * i), col = cols3)
  points(seq(-0.125, 0.125, 0.1), rep(189.5, 3), pch = 19, cex = 0.15 + (0.12 * i), col = cols4)
}

## balls
loc <- c(runif(8, -0.45, 0.45), runif(8, 0, 30))
points(loc[1:8], loc[9:16], pch = 19, cex = round(runif(8, 1.8, 2.2), digits = 2), 
       col = sample(ball.col, 8, replace = TRUE))
loc <- c(runif(6, -0.25, 0.25), runif(6, 65, 100))
points(loc[1:6], loc[7:12], pch = 19, cex = round(runif(6, 1.8, 2.2), digits = 2), 
       col = sample(ball.col, 6, replace = TRUE))
loc <- c(runif(4, -0.15, 0.15), runif(4, 125, 170))
points(loc[1:4], loc[5:8], pch = 19, cex = round(runif(4, 1.8, 2.2), digits = 2),
       col = sample(ball.col, 4, replace = TRUE))
points(-0.05, 200, pch = 19, cex = 2, col = sample(ball.col, 1))
# first layer edge
points(sample(c(-0.45, 0.45), 1), 6, pch = 19, cex = 2, col = sample(ball.col, 1))
# second layer edge
points(sample(c(-0.32, 0.32), 1), 66, pch = 19, cex = 2, col = sample(ball.col, 1))
# third layer edge
points(sample(c(-0.2, 0.2), 1), 130, pch = 19, cex = 2, col = sample(ball.col, 1))

# Star
if (star == 'star') {
  points(-0.02, 251, pch = 19, cex = 4.3, col = 7)
  points(-0.02, 254, pch =  emo::ji(keyword = 'star'), cex = 8, col =  7)
} else if (star == 'heart') {
  points(-0.02, 251, pch =  emo::ji(keyword = 'heart'), cex = 5, col =  'hotpink')
} else if (star == 'poop') {
  points(-0.02, 253, pch = 19, cex = 5, col = 'black')
  points(-0.02, 256, pch =  emo::ji(keyword = 'poop'), cex = 6, col =  'chocolate4')
} else if (star == 'smile') {
  points(-0.02, 253, pch = 19, cex = 7.2, col = rgb(1, 1, 0, alpha = 0.95))
  points(-0.02, 253, pch =  emo::ji(keyword = 'smile'), cex = 4, col = rgb(0, 0, 0, alpha = 0.8))
} else if (star == 'smirk') {
  points(-0.02, 253, pch = 19, cex = 7.2, col = rgb(1, 1, 0, alpha = 0.95))
  points(-0.02, 253, pch =  emo::ji(keyword = 'smirk'), cex = 4, col = rgb(0, 0, 0, alpha = 0.8))
} else {
  stop("This star type is not supported.")
}

# Snowflakes
if (snow.level == 'none') {
  snow_num <- c(0, 0)
} else if (snow.level == 'below_normal') {
  snow_num <- c(40, 60)
} else if (snow.level == 'normal') {
  snow_num <- c(80, 100)
} else if (snow.level == 'above_normal') {
  snow_num <- c(160, 200)
}
points(runif(snow_num[1], -1, 1), runif(snow_num[1], -50, ll + 60), 
       col = sample(snow.col, snow_num[1], replace = TRUE), 
       cex = sample(seq(0.6, 1.5, length.out = snow_num[1])), 
       pch = emo::ji(keyword = 'snowflake'))
points(runif(snow_num[2], -1, 1), runif(snow_num[2], -50, -45), 
       col = sample(snow.col, snow_num[2], replace = TRUE), 
       cex = sample(seq(0.6, 1.5, length.out = snow_num[2])), 
       pch = emo::ji(keyword = 'snowflake'))

  # Add author
  mtext(paste0(author, ' - ', title), side = 1, line = 1, col = title.col)

  if (!is.null(fileout)) {
    dev.off()
  }
}
