     (when (:debug options)
       (def log log-debug))
     (if proceed?
-      (do
+      (let [options {:n-customers    (:customers options)
+                     :n-reservations (:reservations options)
+                     :n-relations    (:relations options)
+                     :n-queries      (:queries options)}]
         (log "options: " options)
         options)
       nil)))
 
 ; HELPER FUNCTIONS
 
-(defn initialize-data [n-customers n-reservations n-relations]
+(defn zip [& lists]
+  "Zip m lists of n elements into list of n m-tuples.
+
+  > (zip [1 2 3 4] [5 6 7 8] [9 10 11 12])
+  ([1 5 9] [2 6 10] [3 7 11] [4 8 12])
+
+  Based on http://stackoverflow.com/a/2588385/8137"
+  (apply map vector lists))
+
+(defn random-subset [n coll]
+  "Get `n` randomly picked elements from `coll`.
+  Sampling is done with replacement, i.e. elements may appear multiple times."
+  (repeatedly n #(rand-nth coll)))
+
+(defn initialize-data [{:keys [n-reservations n-relations]}]
   (letfn [(rand-seats []
-            (* (+ (rand 5) 1) 100))
+            (* (+ (rand-int 5) 1) 100))
           (rand-price []
-            (+ (* (rand 5) 10) 50))
+            (+ (* (rand-int 5) 10) 50))
           (generate-relation []
             (for [i (range n-relations)]
               (ref {:id i :seats (rand-seats) :price (rand-price)})))]

(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)
          customer-actors
            (map #(spawn customer-behavior %) (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."))
      (doseq [[customer reservation done?] (zip (cycle customer-actors) reservations done-promises)]
        (send customer reservation done?))
      (doseq [done? done-promises]
        (deref done?))))
  (shutdown-agents))

     :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
