#!/usr/bin/env python3
import threading
import time
import random

class CuentaAhorrosSimple:
    """Solución básica donde clientes pequeños pueden adelantar a clientes grandes."""
    def __init__(self, balance_inicial=0):
        self.balance = balance_inicial
        self.mutex = threading.Lock()
        self.saldo_suficiente = threading.Condition(self.mutex)

    def depositar(self, cantidad, id_cliente):
        with self.mutex:
            self.balance += cantidad
            print(f"[+] Depósito: Cliente {id_cliente} ingresa {cantidad}€. Balance actual: {self.balance}€")
            self.saldo_suficiente.notify_all()

    def sacar(self, cantidad, id_cliente):
        with self.mutex:
            while self.balance < cantidad:
                print(f"[-] Espera:   Cliente {id_cliente} quiere sacar {cantidad}€ pero hay {self.balance}€.")
                self.saldo_suficiente.wait()
            self.balance -= cantidad
            print(f"[✓] Retirada: Cliente {id_cliente} retira {cantidad}€. Balance restante: {self.balance}€")


class CuentaAhorrosJusta:
    """Solución con orden FIFO estricto mediante turnos numerados para evitar adelantamientos."""
    def __init__(self, balance_inicial=0):
        self.balance = balance_inicial
        self.turno_actual = 0
        self.siguiente_turno = 0
        self.mutex = threading.Lock()
        self.cola_turnos = threading.Condition(self.mutex)

    def depositar(self, cantidad, id_cliente):
        with self.mutex:
            self.balance += cantidad
            print(f"[+] Depósito Justo: Cliente {id_cliente} ingresa {cantidad}€. Balance actual: {self.balance}€")
            self.cola_turnos.notify_all()

    def sacar(self, cantidad, id_cliente):
        with self.mutex:
            mi_turno = self.siguiente_turno
            self.siguiente_turno += 1
            print(f"[#] Turno {mi_turno}: Cliente {id_cliente} toma turno para retirar {cantidad}€.")

            # Solo puede retirar si es su turno Y hay saldo suficiente
            while mi_turno != self.turno_actual or self.balance < cantidad:
                self.cola_turnos.wait()

            self.balance -= cantidad
            self.turno_actual += 1
            print(f"[✓] Retirada Justa (Turno {mi_turno}): Cliente {id_cliente} retira {cantidad}€. Balance: {self.balance}€")
            self.cola_turnos.notify_all()


def demo():
    print("=== DEMOSTRACIÓN: CUENTA DE AHORROS JUSTA (ORDEN FIFO) ===")
    cuenta = CuentaAhorrosJusta(balance_inicial=100)

    # Cliente 1 pide una cantidad grande (500€) y esperará
    t1 = threading.Thread(target=cuenta.sacar, args=(500, "Grande"), name="H-Grande")
    # Cliente 2 llega después y pide solo 50€ (hay saldo, pero su turno es posterior)
    t2 = threading.Thread(target=cuenta.sacar, args=(50, "Pequeño"), name="H-Pequeño")

    t1.start()
    time.sleep(0.1)
    t2.start()
    time.sleep(0.2)

    # Depósito de 100€ (insuficiente para Cliente 1)
    d1 = threading.Thread(target=cuenta.depositar, args=(100, "Ingreso-1"), name="H-Dep1")
    d1.start()
    d1.join()
    time.sleep(0.2)

    # Depósito de 400€ (completa los 500€ de Cliente 1)
    d2 = threading.Thread(target=cuenta.depositar, args=(400, "Ingreso-2"), name="H-Dep2")
    d2.start()
    d2.join()

    t1.join()
    t2.join()
    print("=== FIN DE LA DEMOSTRACIÓN ===")


if __name__ == "__main__":
    demo()
