 (defn -main [& args]
   "Main function. `args` should be a list of command line arguments."
   (when-let [options (parse-args args)]
-    (let [{n-customers    :customers
-           n-reservations :reservations
-           n-relations    :relations} options
-          {:keys [reservations cars flights rooms]}
-            (initialize-data n-customers n-reservations n-relations)
+    (let [{:keys [n-customers n-reservations]} options
+          {:keys [reservations cars flights rooms] :as data}
+            (initialize-data options)
           customer-actors
-            (map #(spawn customer-behavior %) (range n-customers))
+            (map #(spawn customer-behavior % data options) (range n-customers))
           done-promises
             (repeatedly n-reservations promise)]
       (when (not= (rem n-reservations n-customers) 0)
         (println "WARNING: number of reservations is not divisible by number"
           "of customers."))
+      (log "reservations:" reservations)
+      (log "cars:" cars)
+      (log "flights:" flights)
+      (log "rooms:" rooms)
       (doseq [[customer reservation done?] (zip (cycle customer-actors) reservations done-promises)]
         (send customer reservation done?))
       (doseq [done? done-promises]
-        (deref done?))))
+        (deref done?))
+      (log "reservations:" reservations)
+      (log "cars:" cars)
+      (log "flights:" flights)
+      (log "rooms:" rooms)))
+  (Thread/sleep 1000) ; XXX
   (shutdown-agents))

    (when (:debug options)
      (def log log-debug))
    (if proceed?
      (do
        (log "options: " options)
        options)
      nil)))

; HELPER FUNCTIONS

(defn initialize-data [n-customers n-reservations n-relations]
  (letfn [(rand-seats []
            (* (+ (rand 5) 1) 100))
          (rand-price []
            (+ (* (rand 5) 10) 50))
          (generate-relation []
            (for [i (range n-relations)]
              (ref {:id i :seats (rand-seats) :price (rand-price)})))]

     :flights (generate-relation)
     :rooms   (generate-relation)}))

(defn- look-for-seats [relations n-seats]
  "Look for a relation with `n-seats` free seats in `relations`, and return the
  one with the minimal price. Return nil if no such relation can be found."
  ; Vacation searches for the maximal price, we return the cheapest option. This
  ; makes more sense to me: you want the reserve the cheapest car/flight/room.
  (dosync
    (->> relations
      (filter #(>= (:seats (deref %)) n-seats))
      (sort-by (comp :price deref))
      (first))))

(defn reserve [reservation relation n-seats]
  "Reserve `n-seats` on `relation`, for `reservation`.

  Decreases the number of seats in `relation`, and updates the total in
  `reservation`."
  (dosync
    (alter relation update :seats - n-seats)
    (alter reservation update :total + (:price @relation))))

(defn process-reservation
  [reservation
   {:keys [cars flights rooms]}
   {:keys [n-queries]}]
  "Process a reservation: find a car, flight, and room for the reservation and
  update the data structures."
  ; TODO: does vacation always only reserve 1 seat?
  (dosync
    (let [found-car    (look-for-seats (random-subset n-queries cars) 1)
          found-flight (look-for-seats (random-subset n-queries flights) 1)
          found-room   (look-for-seats (random-subset n-queries rooms) 1)]
      (when found-car    (reserve reservation found-car 1))
      (when found-flight (reserve reservation found-flight 1))
      (when found-room   (reserve reservation found-room 1))
      (alter reservation assoc :fulfilled?
        (some? (or found-car found-flight found-room))))))
; TODO: add some logging

; BEHAVIORS

; TODO: this is not really a customer, but more a manager.
; TODO: what is a customer then? what is the difference between a customer and
; a reservation?
(def customer-behavior
  (behavior
    [customer-id data options]
    [reservation done?]
    (log "start reservation" (:id @reservation) "by customer" customer-id)
    (process-reservation reservation data options)
    (deliver done? true)))

; MAIN
