<?php

/** @noinspection PhpUndefinedMethodInspection */
/** @noinspection PhpUnhandledExceptionInspection */

declare(strict_types=1);

use React\EventLoop;
use React\Promise;

/**
 * Awaits a promise in blocking manner.
 * @param Promise\PromiseInterface $promise
 * @param EventLoop\LoopInterface $loop
 * @return mixed
 */
function await(Promise\PromiseInterface $promise, EventLoop\LoopInterface $loop)
{
	$resolved = $failed = $started = false;
	$value = null;
	$futureTicks = 0;

	$promise->then(
		function($argument = null) use(&$resolved, &$value, &$started, $loop): void
		{
			$resolved = true;
			$value = $argument;
			$started && $loop->stop();
		},
		function($error = null) use(&$failed, &$value, &$started, $loop): void
		{
			$failed = true;
			$value = $error;
			$started && $loop->stop();
		}
	);

	// Don't run internal loop on fulfilled promises.
	if (!$resolved && !$failed) {

		(function() use(&$resolved, &$failed, &$futureTicks): void {

			if (!property_exists($this, 'running')
				|| !property_exists($this, 'futureTickQueue')
				|| !property_exists($this, 'timers')) {
				throw new LogicException(sprintf('%s() does\'t support %s implementation.', __FUNCTION__, get_class($this)));
			}

			if (!$this->running) {
				throw new RuntimeException('Event loop is not running.');
			}

			// Pending future ticks, we'll add them later.
			(function() use(&$futureTicks): void {
				$futureTicks = count($this->queue);
			})->call($this->futureTickQueue);

			// If there is a timer on schedule, remove it so it won't get executed again.
			(function() {

				foreach ($this->schedule as $id => $scheduled) {

					if ($scheduled >= $this->time) {
						break;
					}

					if ($this->timers[$id]->isPeriodic() && isset($this->timers[$id]->timers[$id])) {
						$this->schedule[$id] = $this->timers[$id]->getInterval() + $this->time;
						$this->sorted = false;
					} else {
						unset($this->timers[$id], $this->schedule[$id]);
					}

					break;

				}

			})->call($this->timers);

		})->call($loop);

		/**
		 * Used in promise handler.
 		 * @noinspection PhpUnusedLocalVariableInspection
		 */
		$started = true;

		$loop->run();

		(function() use(&$resolved, &$failed, &$futureTicks): void {

			$this->running = true;

			// Main loop tick queue would underflow, fill it with noop.
			for ($i = 0; $i < $futureTicks; $i++) {
				$this->futureTickQueue->add(function() {});
			}

		})->call($loop);

	}

	// Hide throwable from inspections.
	(function() use($failed, $value): void {
		if ($failed && $value instanceof Throwable) {
			throw $value;
		}
	})();

	return $value;
}