#lang racket/base

(require
  racket/list
  json
  )
(provide
  pizza-name pizza-price pizza-ingredients-fr
  pizzeria-menu pizzeria-name
  make-pizzeria-generator pizzeria-generate
  )

; Pizzeria generator
; Generate a pizzeria and its menu

;;;;
; Ingredient structure
(struct ingredient
  (name-fr ; French name
   price ; Price in €
   frequency ; Weight when choosing ingredients
  ))

; Empty ingredient - used as a filler if there is no ingredient of a category left for a recipe
(define *empty-ingredient* (ingredient "" 0 0))

; Json deserialisation
(define (jsexpr->ingredient js)
  (ingredient
    (hash-ref js 'name-fr)
    (hash-ref js 'price)
    (hash-ref js 'frequency)))

;;;;
; Pizza structure
(struct pizza
  (ingredients ; The list of ingredients
   name ; Name of the pizza
   ))

; Price of a pizza
(define (pizza-price pzz)
  (foldl
    (lambda (i res)
      (+ (ingredient-price i) res))
    0
    (pizza-ingredients pzz)))

; List of ingredients as a string - French names
(define (pizza-ingredients-fr pzz)
  (foldl
    (lambda (i res)
      (define name (ingredient-name-fr i))
      (cond
        ((equal? name "") res)
        ((equal? res "") name)
        (#t (string-append res ", " name))))
    ""
    (pizza-ingredients pzz)))

;;;;
; Pizzaria structure
(struct pizzeria
  (menu ; List of pizzas
   name ; name of the pizzeria
   ))

;;;;
; Range structure
(struct range
  (min ; Minimal value of a range
   max ; Maximal value of a range
   ))

; Generate a number in the range
(define (range-generate rng)
  (random (range-min rng) (+ 1 (range-max rng))))

; Json deserialisation
(define (jsexpr->range js)
  (range
    (hash-ref js 'min)
    (hash-ref js 'max)))

;;;;
; Recipe structure
(struct recipe
  (ingredients ; A list of ingredient categories
   frequency ; Weight when choosing recipes
   ))

; Json deserialisation
(define (jsexpr->recipe js)
  (recipe
    (map string->symbol (hash-ref js 'ingredients))
    (hash-ref js 'frequency)))

;;;;
; Generator structure
(struct pizzeria-generator
  (ingredients ; A table category -> list of ingredients
   recipes ; A list of generic recipes for pizzas
   ingredient-variety ; A table category -> range indicating the number of ingredients a pizzeria can have for each category
   recipe-variety ; A range indicating the number of different pizzas a pizzeria can have
   name-list ; A list of possible pizza names
  ))

; Json deserialisation
(define (jsexpr->pizzeria-generator js)
  (pizzeria-generator
    ; ingredients
    (make-hash
      (hash-map
        (hash-ref js 'ingredients)
        (lambda (k v)
          (cons k (map jsexpr->ingredient v)))))
    ; recipes
    (map jsexpr->recipe (hash-ref js 'recipes))
    ; ingredient-variety
    (make-hash
      (hash-map
        (hash-ref js 'ingredient-variety)
        (lambda (k v)
          (cons k (jsexpr->range v)))))
    ; recipe-variety
    (jsexpr->range (hash-ref js 'recipe-variety))
    ; Name list
    (hash-ref js 'names)))

; Generate from a file
(define (make-pizzeria-generator path)
  (jsexpr->pizzeria-generator (read-json (open-input-file path))))

; Pizzeria generation function
(define (pizzeria-generate pzz-gen)
  ; Max number of pizza on the menu
  (define nb-pizzas (range-generate (pizzeria-generator-recipe-variety pzz-gen)))
  ; Ingredients of the pizzeria
  ; For each category, restrict the ingredient list with a number choosen in the range of the category
  (define ingredients
    (make-hash
      (hash-map
        (pizzeria-generator-ingredients pzz-gen)
        (lambda (k v)
          (define nb-ingredients (range-generate (hash-ref (pizzeria-generator-ingredient-variety pzz-gen) k)))
          (cons k (take (shuffle v) nb-ingredients))))))
  ; Pizza recipe generation
  (define (make-recipe template ingredients-left [res '()])
    (cond
      ((null? template)
       (reverse res))
      (#t
       (define category (car template))
       (define lst-ingredients (shuffle (hash-ref ingredients-left category)))
       (define ingredient (or (and (not (null? lst-ingredients)) (car lst-ingredients))
                              *empty-ingredient*))
       ; Remove the choosen ingredient from the table
       (when (not (null? lst-ingredients))
         (hash-set! ingredients-left category (cdr lst-ingredients)))
       ; Continue with the next element of the template
       (make-recipe
         (cdr template)
         ingredients-left
         (cons ingredient res)))))
  ; Pizza names
  (define names (list->vector (take (shuffle (pizzeria-generator-name-list pzz-gen)) nb-pizzas)))
  ; Generate the pizzeria
  (pizzeria
    (build-list
      nb-pizzas
      (lambda (k)
        (pizza
          (make-recipe
            (recipe-ingredients (car (shuffle (pizzeria-generator-recipes pzz-gen))))
            (hash-copy ingredients))
          (vector-ref names k))))
    "Chez Feufo - Pizzas"))
