<?php

/**
 * Vvveb
 *
 * Copyright (C) 2022  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Vvveb\System;

trait EventTrait {
	/**
	 * @param string $method
	 * @param mixed $params
	 * @return mixed
	 */
	public static function trigger(string $method, mixed $params = null): mixed {
		if ($params === null) {
			return $params = \Vvveb\System\Event::trigger(__CLASS__, $method);
		} else {
			return $params = \Vvveb\System\Event::trigger(__CLASS__, $method, $params);
		}
	}

	/**
	 * @param string $namespace
	 * @param string $name
	 * @param string $id
	 * @param callable $callback
	 * @param int $priority
	 * @return void
	 */
	public static function on(string $namespace, string $name, string $id, callable $callback, int $priority = 0): void {
		\Vvveb\System\Event::on($namespace, $name, __CLASS__ . '::' . $id, $callback, $priority);
	}
}

class Event {
	/** @var array<string, array<string, array<int, array<string, callable>>>> */
	private static array $events = [];

	private function __construct() {
	}

	private function __clone() {
	}

	/**
	 * @param string $namespace
	 * @param string $name
	 * @param string $id
	 * @param callable $callback
	 * @param int $priority
	 * @return void
	 */
	public static function on(string $namespace, string $name, string $id, callable $callback, int $priority = 1000): void {
		if (empty(self::$events[$namespace][$name][$priority])) {
			self::$events[$namespace][$name][$priority] = [];
		}

		self::$events[$namespace][$name][$priority][$id] = $callback;
		ksort(self::$events[$namespace][$name], SORT_NUMERIC);
	}

	/**
	 * Trigger all callbacks registered for the given namespace and name.
	 * Additional arguments are passed to each callback.
	 *
	 * @param string $namespace
	 * @param string $name
	 * @param mixed ...$args
	 * @return mixed
	 */
	public static function trigger(string $namespace, string $name, mixed ...$args): mixed {
		$hasParams = false;
		$params    = $args;

		if (empty(self::$events[$namespace][$name])) {
			return $params;
		}

		//$params can be changed by events and be empty, set flag so that we know to return it
		if ($params || isset($params[0])) {
			$hasParams = true;
		}

		foreach (self::$events[$namespace][$name] as $priority => $events) {
			foreach ($events as $id => $event) {
				if (is_callable($event)) {
					if ($hasParams) {
						$ret = call_user_func_array($event, $params);
						//if event does not filter parameters or someone forgot to return them keep previous parameters
						if ($ret !== null) {
							$params = $ret;
						}
					} else {
						call_user_func($event);
					}
				}
			}
		}

		if ($hasParams) {
			return $params;
		}

		return null;
	}

	/**
	 * @param string $namespace
	 * @param string|false $name
	 * @param string|false $eventId
	 * @return bool
	 */
	public static function off(string $namespace, string|false $name = false, string|false $eventId = false): bool {
		if ($eventId && ! empty(self::$events[$namespace][$name])) {
			foreach (self::$events[$namespace][$name] as $priority => &$events) {
				foreach ($events as $id => $event) {
					if ($id === $eventId) {
						unset($events[$id]);

						break 2;
					}
				}
			}
		} else {
			if ($name && ! empty(self::$events[$namespace][$name])) {
				unset(self::$events[$namespace][$name]);

				return true;
			} else {
				if ($namespace && ! empty(self::$events[$namespace])) {
					unset(self::$events[$namespace]);

					return true;
				}
			}
		}

		return false;
	}

	/**
	 * @param string|false $namespace
	 * @param string|false $name
	 * @return array
	 */
	public static function getEvents(string|false $namespace = false, string|false $name = false): array {
		if ($name) {
			return self::$events[$namespace][$name] ?? [];
		} else {
			if ($namespace) {
				return self::$events[$namespace] ?? [];
			}
		}

		return self::$events;
	}
}
