import asyncio
import itertools
import random
from dataclasses import dataclass, field
from time import monotonic

@dataclass(order=True)
class TascaManteniment:
    """Representa una ordre de manteniment amb prioritat."""
    prioritat: int  # 1 per a Crítica (Alta), 2 per a Preventiva (Normal)
    id_maquina: int = field(compare=False)
    temps_reparacio: float = field(compare=False)

async def tecnic_manteniment(id_tecnic: int, cua: asyncio.PriorityQueue):
    """Consumidor: Simula un tècnic reparant màquines."""
    continuar = True
    while continuar:
        # Obtenir la tasca amb el número de prioritat més baix (més urgent)
        try:
            tasca = await cua.get()
            tipus = "CRÍTICA" if tasca.prioritat == 1 else "PREVENTIVA"
        
            print(f"[Tècnic {id_tecnic}] Iniciant reparació {tipus} a màquina {tasca.id_maquina}...")
        
            # Simulem el temps de treball
            await asyncio.sleep(tasca.temps_reparacio)
        
            print(f"[Tècnic {id_tecnic}] Reparació màquina {tasca.id_maquina} finalitzada.")
        
            # Indiquem a la cua que la tasca s'ha completat
            cua.task_done()
        except asyncio.CancelledError:
            print(f"[Tècnic {id_tecnic}] Tasca cancel·lada")
            continuar = False

async def monitor_sistema(cua: asyncio.PriorityQueue, total_tasques: int):
    """Productor: Genera peticions de manteniment segons l'estat dels sensors."""
    gt = generador_tasques((1, 2), (0.1, 1.5), (0.1, 0.4), range(total_tasques))
    for tasca, retard in gt:
        print(f"[Monitor] Generada tasca {tasca}. Esperant per encuar...")
        
        # Si la cua és plena (maxsize), aquesta crida es bloquejarà (backpressure)
        await cua.put(tasca)
        
        print(f"[Monitor] Tasca {tasca.id_maquina} posada a la cua. Mida actual: {cua.qsize()}")

        # Simulem un petit interval entre fallades de sensors
        #await asyncio.sleep(retard)


def generador_tasques(prioritats, temps_manteniment, temps_entre_tasques, ids):
    for i in ids:
        prioritat = random.choice(prioritats)
        temps = random.uniform(*temps_manteniment)
        tasca = TascaManteniment(prioritat=prioritat, id_maquina=i, temps_reparacio=temps)
        retard = random.uniform(*temps_entre_tasques)
        yield tasca, retard


async def main():
    # Cua de prioritat amb capacitat limitada per protegir la memòria del sistema
    cua_tasques = asyncio.PriorityQueue(maxsize=5)
    num_tecnics = 3
    total_ordres = 15
    inici = monotonic()

    # Iniciem els tècnics (consumidors) com a tasques de fons
    tecnics = [asyncio.create_task(tecnic_manteniment(i, cua_tasques)) 
               for i in range(num_tecnics)]

    # Executem el productor i esperem que generi totes les ordres
    await monitor_sistema(cua_tasques, total_ordres)

    # Esperem que totes les tasques de la cua s'hagin processat completament
    await cua_tasques.join()

    # Cancel·lem els tècnics, ja que són bucles infinits
    for t in tecnics:
        t.cancel()

    print(f"\nManteniment completat en {monotonic() - inici:.2f} segons.")

    await asyncio.gather(*tecnics)


if __name__ == "__main__":
    asyncio.run(main())
