<?php

namespace App\SSE;

/**
 * Small class to abstract Server-Sent Events (SSE)
 * 
 * https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
 */
class EventLoop 
{
    public int $interval  = 3;

    public int $heartbeat = 5; // send heartbeat every num seconds to ensure connection is still alive
    public int $timeLimit = 3600;
    public int $execLimit = 30;

    public function start(callable $callback): void
    {
        // as session data is locked to prevent concurrent writes we
        // make it read only to prevent the server from locking up
        if (session_status() === PHP_SESSION_ACTIVE)
        {
            session_write_close();
        }

        header('Content-Type: text/event-stream');
        // explicitly disable caching so varnish and other upstreams won't cache.
        header('Cache-Control: no-cache, must-revalidate');
        // instruct nginx to disable fastcgi_buffering and disable gzip
        header('X-Accel-Buffering: no');

        // send headers to client indicating we are now a stream
        ob_end_flush();
        flush();

        $expirationTime = time() + $this->timeLimit;

        $lastHeartbeat = time();

        while (!connection_aborted() && time() < $expirationTime)
        {
            set_time_limit($this->execLimit);
            try {
                $data = call_user_func($callback);
                if ($data !== NULL)
                {
                    $this->send($data);
                    $lastHeartbeat = time();
                }
            } catch (StopEventLoopException $th) {
                break;
            }

            // sleep and perform heartbeat to ensure connection is still alive
            for ($i = 0; $i < $this->interval; $i++)
            {
                if (time() >= $lastHeartbeat + $this->heartbeat)
                {
                    echo ": \n\n";
                    ob_end_flush();
                    flush();
                    $lastHeartbeat = time();
                }
                sleep(1);
            }
        }
    }

    /**
     * Send data to client encoded as json
     */
    private function send($data): void
    {
        echo "data: " . json_encode($data);
        echo "\n\n";
        // send data to stream
        ob_end_flush();
        flush();
    }
}