;TRANSACCIONES BANCARIAS CON ATOMOS
(defn transferirDinero [desde hasta cantidad]
  (when (>= @desde cantidad)
    (swap! desde - cantidad)
    ;Ponemos un sleep para que la transacción sea más realista
    (Thread/sleep (rand-int 15))
    (swap! hasta + cantidad)
  )
)
 
;Realiza transacciones random
(defn transaccionesRandom [cuenta1 cuenta2 n]
  (dotimes [_ n]
    (let [[desde hasta] (shuffle [cuenta1 cuenta2])]
      (transferirDinero desde hasta (rand-int @desde))
    )
  )
 (printResultados)
)

;Resetea cuentas
(defn resetearCuentas! []
  (reset! cuenta-a 50)
  (reset! cuenta-b 50)
)

;Realiza un print del estado de las cuentas y su suma
(defn printResultados []
  (println "Estado cuenta A:" @cuenta-a)
  (println "Estado cuenta B:" @cuenta-b)
  (println "Suma de Cuentas:" (+ @cuenta-a @cuenta-b))
  (println "")
)

;Lanza N threads realizando M transacciones con cada uno.
;Muestra el estado de las cuentas al finalizar las M transacciones de c/thread.
(defn transaccionesMultiplesThreads [nThreads nTransaccionesXThread]
  (let [futures
        (doall
          (for [_ (range nThreads)]
            (future (transaccionesRandom cuenta-a cuenta-b nTransaccionesXThread))))]
    (while (not (every? future-done? futures))
      (Thread/sleep 100)
    )
  )
)

;Ambas cuentas tienen inicialmente 50 pesos
(def cuenta-a (atom 50))
(def cuenta-b (atom 50))

(println "TRANSACCIONES BANCARIAS CON ATOMOS EN UN UNICO THREAD")
(transaccionesRandom cuenta-a cuenta-b 1000)

(println "TRANSACCIONES BANCARIAS CON ATOMOS EN MULTIPLES THREADS")
(resetearCuentas!)
(transaccionesMultiplesThreads 5 1000)

;Se puede ver que los estados intermedios de las cuentas a medida que van terminando
;los threads es inconsistente.
;Recien al final vemos la consistencia del estado cuando finalizan todos los threads
;porque eran simples sumas y restas