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

class OllaTribu:
    """Monitor para el problema clásico de la tribu salvaje y el cocinero."""
    def __init__(self, capacidad_maxima=5):
        self.capacidad = capacidad_maxima
        self.raciones = capacidad_maxima
        self.mutex = threading.Lock()
        self.olla_vacia = threading.Condition(self.mutex)     # Condición donde espera el cocinero
        self.olla_con_comida = threading.Condition(self.mutex) # Condición donde esperan los salvajes

    def servirse_porcion(self, id_salvaje):
        with self.mutex:
            while self.raciones == 0:
                print(f"[!] Salvaje {id_salvaje}: ¡Olla vacía! Despertando al cocinero...")
                self.olla_vacia.notify()             # Despierta al cocinero
                self.olla_con_comida.wait()          # Espera a que se cocine el festín

            self.raciones -= 1
            print(f"[🍽️] Salvaje {id_salvaje} se sirve una ración. Quedan {self.raciones} raciones.")

    def rellenar_olla(self):
        with self.mutex:
            while self.raciones > 0:
                self.olla_vacia.wait()               # El cocinero duerme si aún queda comida

            print("[🔥] Cocinero: ¡Despierto! Rellenando la olla con un nuevo festín...")
            time.sleep(0.1)                          # Simula tiempo de cocinado
            self.raciones = self.capacidad
            print(f"[🍲] Cocinero: Olla rellenada al máximo ({self.capacidad} raciones). Avisando a los comensales.")
            self.olla_con_comida.notify_all()        # Notifica a los salvajes esperando


def salvaje(olla, id_salvaje, veces_comer):
    for i in range(veces_comer):
        time.sleep(random.uniform(0.05, 0.15)) # Salvaje cazando/danzando
        olla.servirse_porcion(id_salvaje)


def cocinero(olla, stop_event):
    while not stop_event.is_set():
        olla.rellenar_olla()


def main():
    print("=== PROBLEMA DE LA OLLA DE LA TRIBU (MONITORES) ===")
    capacidad = 4
    num_salvajes = 6
    comidas_por_salvaje = 3
    olla = OllaTribu(capacidad_maxima=capacidad)

    stop_cocinero = threading.Event()
    hilo_cocinero = threading.Thread(target=cocinero, args=(olla, stop_cocinero), daemon=True)
    hilo_cocinero.start()

    hilos_salvajes = []
    for i in range(num_salvajes):
        t = threading.Thread(target=salvaje, args=(olla, i + 1, comidas_por_salvaje))
        hilos_salvajes.append(t)
        t.start()

    for t in hilos_salvajes:
        t.join()

    print("=== TODOS LOS SALVAJES HAN COMIDO SATISFACTORIAMENTE ===")


if __name__ == "__main__":
    main()
