#!/usr/bin/env catnip
# Barres de progression avec tqdm
# tqdm affiche une barre de progression sur n'importe quel itérable
#
# DEPS: tqdm

tqdm = import('tqdm')
time = import('time')
random = import('random')

random.seed(42)

# Types somme : ensembles fermés de catégories, explicites et vérifiés à l'exhaustivité
# par le linter dans les match. La méthode label() rend la forme d'affichage.

union Kind {
    sensor; log; metric; event

    label(self): str => {
        match self {
            Kind.sensor => { "sensor" }
            Kind.log    => { "log" }
            Kind.metric => { "metric" }
            Kind.event  => { "event" }
        }
    }
}

union Status {
    ok; warning; critical; anomaly

    label(self): str => {
        match self {
            Status.ok       => { "ok" }
            Status.warning  => { "warning" }
            Status.critical => { "critical" }
            Status.anomaly  => { "anomaly" }
        }
    }
}

union Tag {
    high; mid; low

    label(self): str => {
        match self {
            Tag.high => { "high" }
            Tag.mid  => { "mid" }
            Tag.low  => { "low" }
        }
    }
}

struct Record {
    id: int; kind: Kind; value: float

    display(self): str => { f"#{self.id} [{self.kind.label()}] {self.value}" }
}

struct Tagged { id: int; tag: Tag; value: float }

# Génération de données

kinds = list(Kind.sensor, Kind.log, Kind.metric, Kind.event)

make_record = (i: int): Record => {
    kind = kinds[random.randint(0, len(kinds) - 1)]
    val = round(random.uniform(0, 100), 2)
    Record(i, kind, val)
}

records = list()
for i in range(200) {
    records.append(make_record(i))
}
print(f"⇒ {len(records)} records générés")
print(f"  Exemple : {records[0].display()}")

# Traitement avec barre de progression

print()
print("⇒ Traitement séquentiel")

process = (record: Record): Status => {
    time.sleep(0.005)
    match True {
        _ if record.value > 95 => { Status.critical }
        _ if record.value > 80 => { Status.warning }
        _ if record.value < 5  => { Status.anomaly }
        _                      => { Status.ok }
    }
}

results = list()
for r in tqdm.tqdm(records, desc="process", unit="rec") {
    results.append(process(r))
}

# Classification des résultats

print()
print("⇒ Résultats")

count_status = (status: Status): int => {
    n = 0
    for r in results {
        if r == status { n = n + 1 }
    }
    n
}

for s in list(Status.ok, Status.warning, Status.critical, Status.anomaly) {
    n = count_status(s)
    bar = "#" * int(n / 2)
    print(f"  {s.label():>10} : {n:>3}  {bar}")
}

# Pipeline multi-étapes

print()
print("⇒ Pipeline multi-étapes")

struct Stage {
    name: str; fn

    run(self, data) => {
        out = list()
        for item in tqdm.tqdm(data, desc=self.name, unit="rec", leave=False) {
            out.append(self.fn(item))
        }
        out
    }
}

normalize = (r: Record): Record => {
    time.sleep(0.002)
    Record(r.id, r.kind, round(r.value / 100, 4))
}

classify_tag = (value: float): Tag => {
    match True {
        _ if value > 0.8 => { Tag.high }
        _ if value > 0.2 => { Tag.mid }
        _                => { Tag.low }
    }
}

tag = (r: Record): Tagged => {
    time.sleep(0.002)
    Tagged(r.id, classify_tag(r.value), r.value)
}

pipeline = list(
    Stage("normalize", normalize),
    Stage("tag", tag),
)

data = records
for stage in pipeline {
    data = stage.run(data)
}

# Comptage par tag

print()
print("⇒ Distribution après pipeline")
for variant in list(Tag.high, Tag.mid, Tag.low) {
    n = 0
    for t in data {
        if t.tag == variant { n = n + 1 }
    }
    print(f"  {variant.label():>5} : {n}")
}

# Barre manuelle (tqdm.tqdm sans itérable)

print()
print("⇒ Barre manuelle")
pbar = tqdm.tqdm(total=50, desc="upload", unit="chunk")
i = 0
while i < 50 {
    time.sleep(0.01)
    pbar.update(1)
    i = i + 1
}
pbar.close()

print()
print("⇒ Terminé")