<?php

declare(strict_types=1);

namespace mirzaev\huesos;

// Files of the project
use mirzaev\huesos\controllers\core as controller,
	mirzaev\huesos\models\core as model,
	mirzaev\huesos\models\socket;

// Framework for PHP
use mirzaev\minimal\core;

// Framework for ArangoDB
use mirzaev\arangodb\connection as arangodb,
	mirzaev\arangodb\collection,
	mirzaev\arangodb\document;

// Library for ArangoDB
use ArangoDBClient\Document as _document;

// Built-in libraries
use exception;

// Server of WebSocket
use OpenSwoole\WebSocket\{Server, Frame};
use OpenSwoole\Constant,
	OpenSwoole\Http\Request,
	OpenSwoole\Table;

// Initialize dependencies
require __DIR__ . DIRECTORY_SEPARATOR
	. '..' . DIRECTORY_SEPARATOR
	. '..' . DIRECTORY_SEPARATOR
	. '..' . DIRECTORY_SEPARATOR
	. '..' . DIRECTORY_SEPARATOR
	. 'vendor' . DIRECTORY_SEPARATOR
	. 'autoload.php';

define('INDEX', __DIR__);

// Initialize the core
$core = new core(namespace: __NAMESPACE__, controller: new controller(false), model: $model = new model(false));

// https://dev.to/robertobutti/websocket-with-php-4k2c

$server = new Server("armng.dev.mirzaev.sexy", 9502, Server::SIMPLE_MODE, Constant::SOCK_TCP | Constant::SSL);

$accounts = new Table(1024);
$accounts->column('id', Table::TYPE_INT, 4);
$accounts->column('type', Table::TYPE_STRING, 16);
$accounts->create();

$server->set([
	// Process
	'daemonize' => 1,
	'user' => 'www-data',
	'group' => 'www-data',
	/* 'chroot' => '/data/server/', */
	'open_cpu_affinity' => true,
	/* 'cpu_affinity_ignore' => [0, 1], */
	'pid_file' => '/var/run/php/ebala-socket.pid',

	// Server
	/* 'reactor_num' => 8,
	'worker_num' => 2, */
	'message_queue_key' => 'mq1',
	'dispatch_mode' => 4,
	'discard_timeout_request' => true,
	/* 'dispatch_func' => 'my_dispatch_function', */

	// Worker
	/* 'max_request' => 0,
	'max_request_grace' => $max_request / 2, */

	// HTTP Server max execution time, since v4.8.0
	'max_request_execution_time' => 5, // 30s

	// Task worker
	/* 'task_ipc_mode' => 2,
	'task_max_request' => 100,
	'task_tmpdir' => '/tmp',
	'task_worker_num' => 8,
	'task_enable_coroutine' => true,
	'task_use_object' => true,   */

	// Logging
	'log_level' => 1,
	'log_file' => __DIR__ . '/../logs/openswoole.log',
	'log_rotation' => Constant::LOG_ROTATION_DAILY,
	'log_date_format' => '%Y-%m-%d %H:%M:%S',
	'log_date_with_microseconds' => false,
	/* 'request_slowlog_file' => false, */

	// Enable trace logs
	'trace_flags' => Constant::TRACE_ALL,

	// TCP
	'input_buffer_size' => 2097152,
	'buffer_output_size' => 32 * 1024 * 1024, // byte in unit
	'tcp_fastopen' => true,
	'max_conn' => 10000,
	'tcp_defer_accept' => false,
	'open_tcp_keepalive' => true,
	'tcp_keepidle' => 30,       // Check if there is no data for 4s.
	/* 'tcp_keepinterval' => 1,   // Check if there is data every 1s */
	'tcp_keepcount' => 10,
	'open_tcp_nodelay' => true,
	/* 'pipe_buffer_size' => 32 * 1024 * 1024, */
	'socket_buffer_size' => 128 * 1024 * 1024,

	// Kernel
	'backlog' => 512,
	'kernel_socket_send_buffer_size' => 65535,
	'kernel_socket_recv_buffer_size' => 65535,

	// TCP Parser
	'open_eof_check' => true,
	'open_eof_split' => true,
	'package_eof' => '\r\n',
	'open_length_check' => true,
	'package_length_type' => 'N',
	'package_body_offset' => 8,
	'package_length_offset' => 8,
	'package_max_length' => 2 * 1024 * 1024, // 2MB
	/* 'package_length_func' => 'my_package_length_func', */

	// Coroutine
	'enable_coroutine' => true,
	'max_coroutine' => 3000,
	'send_yield' => true,

	// tcp server
	'heartbeat_idle_time' => 600,
	'heartbeat_check_interval' => 30,
	'enable_delay_receive' => false,
	'enable_reuse_port' => false,
	'enable_unsafe_event' => false,

	// Protocol
	'open_http_protocol' => true,
	'open_http2_protocol' => true,
	'open_websocket_protocol' => true,
	'open_mqtt_protocol' => false,

	// HTTP2
	'http2_header_table_size' => 4095,
	'http2_initial_window_size' => 65534,
	'http2_max_concurrent_streams' => 1281,
	'http2_max_frame_size' => 16383,
	'http2_max_header_list_size' => 4095,

	// SSL
	'ssl_cert_file' => '/etc/letsencrypt/live/xn--80aksgi6f.xn--24-mlca2chbdebu6a.xn--p1ai/fullchain.pem',
	'ssl_key_file' => '/etc/letsencrypt/live/xn--80aksgi6f.xn--24-mlca2chbdebu6a.xn--p1ai/privkey.pem',
	'ssl_ciphers' => 'ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP',
	'ssl_protocols' => Constant::SSL_TLSv1_3, // added from v4.5.4
	'ssl_verify_peer' => false,
	/* 'ssl_sni_certs' => [
		"cs.php.net" => [
			'ssl_cert_file' => __DIR__ . "/config/sni_server_cs_cert.pem",
			'ssl_key_file' => __DIR__ . "/config/sni_server_cs_key.pem"
		],
		"uk.php.net" => [
			'ssl_cert_file' => __DIR__ . "/config/sni_server_uk_cert.pem",
			'ssl_key_file' => __DIR__ . "/config/sni_server_uk_key.pem"
		],
		"us.php.net" => [
			'ssl_cert_file' => __DIR__ . "/config/sni_server_us_cert.pem",
			'ssl_key_file' =>  __DIR__ . "/config/sni_server_us_key.pem",
		],
	], */

	// Static Files
	'document_root' => __DIR__,
	'enable_static_handler' => true,
	/* 'static_handler_locations' => ['/static', '/app/images'], */
	'http_index_files' => ['index.html', 'index.txt'],

	// Source File Reloading
	'reload_async' => true,
	'max_wait_time' => 30,

	// HTTP Server
	'http_parse_post' => true,
	'http_parse_cookie' => true,
	'upload_tmp_dir' => '/tmp',

	// Compression
	'http_compression' => true,
	'http_compression_level' => 3, // 1 - 9
	'compression_min_length' => 20,

	// Websocket
	'websocket_compression' => true,
	'open_websocket_close_frame' => true,
	'open_websocket_ping_frame' => true, // added from v4.5.4
	'open_websocket_pong_frame' => true, // added from v4.5.4

	// TCP User Timeout
	'tcp_user_timeout' => 120,

	// DNS Server
	'dns_server' => '1.1.1.1',
	/* 'dns_cache_refresh_time' => 60, */
	/* 'enable_preemptive_scheduler' => 0, */

	/* 'open_fastcgi_protocol' => 0, */
	'open_redis_protocol' => 0,

	'event_object' => false,
]);

/* $server->set([
	'ssl_cert_file' => __DIR__
		. DIRECTORY_SEPARATOR . '..'
		. DIRECTORY_SEPARATOR . 'settings'
		. DIRECTORY_SEPARATOR . 'certificates'
		. DIRECTORY_SEPARATOR . '84.22.137.106.pem',
	'ssl_key_file' => __DIR__
		. DIRECTORY_SEPARATOR . '..'
		. DIRECTORY_SEPARATOR . 'settings'
		. DIRECTORY_SEPARATOR . 'certificates'
		. DIRECTORY_SEPARATOR . '84.22.137.106-key.pem'
]); */

$server->on("Start", function (Server $server) use ($core, $model) {
	// Очистка баз данных для сокетов
	collection::truncate($model->arangodb->session, 'socket');
	collection::truncate($model->arangodb->session, 'session_edge_socket');

	// Запись в буфер вывода (журнал)
	echo "Swoole WebSocket Server is started at " . $server->host . ":" . $server->port . "\n";
});

$server->on('Open', function (Server $server, Request $request) use ($accounts, $core, $model) {
	// Инициализация идентификатора
	$id = $request->fd;

	// Запись в базу данных
	$accounts->set((string) $id, [
		'id' => $id
	]);

	// Запись в буфер вывода (журнал)
	echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Connected ({$accounts->count()})" . PHP_EOL;

	// Регистрация
	if (document::write($model->arangodb->session, 'socket', [
		'id' => (int) $id,
		'key' => $key = sodium_bin2hex(sodium_crypto_generichash((string) $id)),
		'expires' => (int) strtotime('+1 hour')
	])) {
		// Создана инстанция сокета (регистрация)

		// Отправка данных для подключения
		$server->push($id, sprintf(
			<<<JSON
				{
					"type": "registration",
					"key": "%s"
				}
				JSON,
			$key
		));
	}
});

$server->on('Message', function (Server $server, Frame $frame) use ($accounts, $core, $model) {
	// Запись в буфер вывода (журнал)
	echo '[' . date('Y.m.d h:i:s', time()) . "][$frame->fd] Message: $frame->data" . PHP_EOL;

	if ($account = socket::account($frame->fd)) {
		// Авторизован аккаунт

		// Инициализация полученных данных
		$message = json_decode($frame->data, false, 2);

		// Инициализация имени
		$name = trim((!empty($account->name['first']) ? mb_strtoupper(mb_substr($account->name['first'], 0, 1)) . '.' : '') . (!empty($account->name['last']) ? ' ' . mb_strtoupper(mb_substr($account->name['last'], 0, 1)) . '.' : '') . (!empty($account->name['second']) ? ' ' . $account->name['second'] : ''), ' ');

		try {
			if ($message->target === 'task') {
				// Заявка

				if ($message->type === 'block') {
					// Блокировка редактирования

					if ($account->status() && ($account->type === 'administrator' || $account->type === 'operator'))
						if (task::block((int) $message->_key, (int) $account->getKey())) {
							// Заблокирована заявка

							// Отправка сообщения всем сокетам
							foreach ($accounts as $key => $value)
								if ((int) $key !== $frame->fd)
									$server->push((int) $key, <<<JSON
									{
										"type": "blocked",
										"target": "task",
										"_key": $message->_key,
										"account": {
											"_key": {$account->getKey()},
											"name": "$name"
										}
									}
									JSON);
						}
				} else if ($message->type === 'unblock') {
					// Разблокировка редактирования

					if ($account->status() && ($account->type === 'administrator' || $account->type === 'operator'))
						if (task::unblock((int) $message->_key, (int) $account->getKey())) {
							// Заблокирована заявка


							// Отправка сообщения всем сокетам
							foreach ($accounts as $key => $value)
								if ((int) $key !== $frame->fd)
									$server->push((int) $key, <<<JSON
									{
										"type": "unblocked",
										"target": "task",
										"_key": $message->_key,
										"account": {
											"_key": {$account->getKey()},
											"name": "$name"
										}
									}
									JSON);
						}
				} else if ($message->type === 'update') {
					// Обновление

					// Отправка сообщения всем сокетам
					foreach ($accounts as $key => $value)
						if ((int) $key !== $frame->fd)
							$server->push((int) $key, <<<JSON
							{
								"type": "updated",
								"target": "task",
								"_key": $message->_key
							}
							JSON);
				} else if ($message->type === 'delete') {
					// Обновление

					// Отправка сообщения всем сокетам
					foreach ($accounts as $key => $value)
						if ((int) $key !== $frame->fd)
							$server->push((int) $key, <<<JSON
							{
								"type": "deleted",
								"target": "task",
								"_key": $message->_key
							}
							JSON);
				}
			}
		} catch (exception $e) {
			var_dump($e);
		}
	}
});

$server->on('Close', function (Server $server, int $id) use ($accounts, $core, $model) {
	// Удаление из базы данных
	$accounts->del((string) $id);

	// Запись в буфер вывода (журнал)
	echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Disconnect (server) ({$accounts->count()})" . PHP_EOL;
});

$server->on('Disconnect', function (Server $server, int $id) use ($accounts, $core, $model) {
	// Удаление из базы данных
	$accounts->del((string) $id);

	// Запись в буфер вывода (журнал)
	echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Disconnect (client) ({$accounts->count()})" . PHP_EOL;
});

// Запуск (точка входа)
$server->start();

