<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;

use App\Classes\Protocol\{Binkp,DNS,EMSI};
use App\Classes\Sock\Exception\SocketException;
use App\Classes\Sock\SocketServer;

class ServerStart extends Command
{
	private const LOGKEY = 'CSS';

	/**
	 * The name and signature of the console command.
	 *
	 * @var string
	 */
	protected $signature = 'server:start';

	/**
	 * The console command description.
	 *
	 * @var string
	 */
	protected $description = 'Start Server';

	/**
	 * Execute the console command.
	 *
	 * @return int
	 * @throws SocketException
	 */
	public function handle(): int
	{
		Log::info(sprintf('%s:+ Server Starting (%d)',self::LOGKEY,getmypid()));

		if (! our_address()->count())
			throw new \Exception('We dont have any ACTIVE FTN addresses assigned');

		$o = Config::get('setup');

		$start = collect();

		if ($o->binkp_active)
			$start->put('binkp',[
				'address'=>$o->binkp_bind,
				'port'=>$o->binkp_port,
				'proto'=>SOCK_STREAM,
				'class'=>new Binkp($o),
			]);

		if ($o->emsi_active)
			$start->put('emsi',[
				'address'=>$o->emsi_bind,
				'port'=>$o->emsi_port,
				'proto'=>SOCK_STREAM,
				'class'=>new EMSI($o),
			]);

		if ($o->dns_active)
			$start->put('dns',[
				'address'=>$o->dns_bind,
				'port'=>$o->dns_port,
				'proto'=>SOCK_DGRAM,
				'class'=>new DNS($o),
			]);

		$children = collect();

		Log::debug(sprintf('%s:# Servers [%d]',self::LOGKEY,$start->count()));

		if (! $start->count()) {
			Log::alert(sprintf('%s:! No servers configured to start',self::LOGKEY));

			return self::FAILURE;
		}

		pcntl_signal(SIGCHLD,SIG_IGN);

		foreach ($start as $item => $config) {
			Log::debug(sprintf('%s:- Starting [%s] (%d)',self::LOGKEY,$item,getmypid()));

			$pid = pcntl_fork();

			if ($pid === -1)
				die('could not fork');

			// We are the child
			if (! $pid) {
				Log::withContext(['pid'=>getmypid()]);
				Log::info(sprintf('%s:= Started [%s]',self::LOGKEY,$item));

				$server = new SocketServer($config['port'],$config['address'],$config['proto']);
				$server->handler = [$config['class'],'onConnect'];

				try {
					$server->listen();

				} catch (SocketException $e) {
					if ($e->getMessage() === 'Can\'t accept connections: "Success"')
						Log::debug(sprintf('%s:! Server Terminated [%s]',self::LOGKEY,$item));
					else
						Log::emergency(sprintf('%s:! Uncaught Message: %s',self::LOGKEY,$e->getMessage()));
				}

				Log::info(sprintf('%s:= Finished: [%s]',self::LOGKEY,$item));

				// Child finished we need to get out of this loop.
				return self::SUCCESS;
			}

			Log::debug(sprintf('%s:- Forked for [%s] (%d)',self::LOGKEY,$item,$pid));
			$children->put($pid,$item);
		}

		// Wait for children to exit
		while ($x=$children->count()) {
			// Wait for children to finish
			$exited = pcntl_wait($status);

			if ($exited < 0)
				abort(500,'Something strange for status: '.serialize($status));

			Log::info(sprintf('%s:= Exited: #%d [%s]',self::LOGKEY,$x,$children->pull($exited)));
		}

		// Done
		Log::debug(sprintf('%s:= Finished.',self::LOGKEY));

		return self::SUCCESS;
	}
}