<?php

declare(strict_types=1);

namespace App\Framework\Async;

use Fiber;

/**
 * Barrier für Synchronisation mehrerer Fibers an einem Punkt
 */
final class AsyncBarrier
{
    private int $waitingCount = 0;

    /** @var array<Fiber> */
    private array $waitingFibers = [];

    private bool $broken = false;

    public function __construct(
        private readonly int $parties,
        private readonly mixed $barrierAction = null
    ) {
    }

    /**
     * Wartet bis alle Parties die Barrier erreichen
     */
    public function await(): void
    {
        if ($this->broken) {
            throw new \RuntimeException("Barrier is broken");
        }

        $this->waitingCount++;

        if ($this->waitingCount < $this->parties) {
            // Noch nicht alle da, warten
            $fiber = new Fiber(function () {
                while ($this->waitingCount < $this->parties && ! $this->broken) {
                    Fiber::suspend();
                }

                if ($this->broken) {
                    throw new \RuntimeException("Barrier was broken while waiting");
                }
            });

            $this->waitingFibers[] = $fiber;
            $fiber->start();
            $fiber->getReturn();
        } else {
            // Alle sind da, führe Barrier-Action aus und wecke alle auf
            if ($this->barrierAction) {
                try {
                    ($this->barrierAction)();
                } catch (\Throwable $e) {
                    $this->broken = true;
                    $this->resumeAllWithException($e);

                    throw $e;
                }
            }

            $this->resumeAll();
            $this->reset();
        }
    }

    /**
     * Bricht die Barrier (alle wartenden Fibers bekommen Exception)
     */
    public function breakBarrier(): void
    {
        $this->broken = true;
        $this->resumeAllWithException(new \RuntimeException("Barrier broken"));
        $this->reset();
    }

    /**
     * Prüft ob Barrier gebrochen ist
     */
    public function isBroken(): bool
    {
        return $this->broken;
    }

    /**
     * Gibt Anzahl wartender Parties zurück
     */
    public function getNumberWaiting(): int
    {
        return $this->waitingCount;
    }

    /**
     * Gibt Anzahl benötigter Parties zurück
     */
    public function getParties(): int
    {
        return $this->parties;
    }

    private function resumeAll(): void
    {
        foreach ($this->waitingFibers as $fiber) {
            if (! $fiber->isTerminated()) {
                $fiber->resume();
            }
        }
    }

    private function resumeAllWithException(\Throwable $exception): void
    {
        foreach ($this->waitingFibers as $fiber) {
            if (! $fiber->isTerminated()) {
                $fiber->throw($exception);
            }
        }
    }

    private function reset(): void
    {
        $this->waitingCount = 0;
        $this->waitingFibers = [];
    }

    /**
     * Gibt Barrier-Statistiken zurück
     */
    public function getStats(): array
    {
        return [
            'parties' => $this->parties,
            'waiting_count' => $this->waitingCount,
            'waiting_fibers' => count($this->waitingFibers),
            'broken' => $this->broken,
        ];
    }
}
