<?php

namespace App\Classes\File;

use Exception;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileEncountered;

use App\Classes\File\Send\Dynamic;
use App\Classes\Node;
use App\Models\Address;

/**
 * Object representing the files we are sending
 *
 * @property-read resource fd
 * @property-read int files_size The size of the files waiting to be sent
 * @property-read int mail_size The size of the mail waiting to be sent
 * @property-read int togo_count The total number of items that havent been sent yet
 * @property-read int total_size The size of the items waiting to be sent
 * @property-read int total_sent
 * @property-read int total_sent_bytes
 */
class Send extends Base
{
	private const LOGKEY = 'IS-';

	public const T_NONE				= 0;
	/** @var int This file contains a file from the DB */
	public const T_FILE				= (1<<0);
	/** @var int This file contains a bundle of Netmail */
	public const T_NETMAIL			= (1<<1);
	/** @var int This file contains a bundle of Echomail */
	public const T_ECHOMAIL			= (1<<2);

	private string $comp_data;
	protected int $size = 0;

	public function __construct()
	{
		// Initialise our variables
		if (get_class($this) === self::class) {
			$this->list = collect();
			$this->f = NULL;
		}
	}

	public function __get($key)
	{
		switch ($key) {
			case 'completed':
				return $this->list
					->filter(function($item) { return $item->complete === TRUE; });

			case 'fd':
				return ! is_null($this->index);

			case 'files_count':
				return $this->list
					->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); })
					->count();

			case 'files_size':
				return $this->list
					->filter(function($item) { return $item->isType(self::IS_FILE|self::IS_TIC); })
					->sum(function($item) { return $item->size; });

			case 'mail_count':
				return $this->list
					->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
					->count();

			case 'mail_size':
				return $this->list
					->filter(function($item) { return $item->isType(self::IS_ARC|self::IS_PKT); })
					->sum(function($item) { return $item->size; });

			case 'name':
			case 'nameas':
			case 'mtime':
			case 'size':
			case 'type':
				return $this->sending->{$key};

			case 'pos':
				return $this->{$key};

			case 'sending':
				return $this->list->get($this->index);

			case 'togo_count':
				return $this->list
					->filter(function($item) { return $item->complete === FALSE; })
					->count();

			case 'total_sent':
				return $this->completed
					->count();

			case 'total_sent_bytes':
				return $this->completed
					->sum(function($item) { return $item->size; });

			case 'total_size':
				return $this->list
					->sum(function($item) { return $item->size; });

			default:
				throw new Exception('Unknown key: '.$key);
		}
	}

	/**
	 * Close the file descriptor of the file we are sending
	 *
	 * @param bool $successful
	 * @param Node $node
	 * @throws Exception
	 */
	public function close(bool $successful,Node $node): void
	{
		if (! $this->fd)
			throw new Exception('No file to close');

		if ($successful) {
			$end = time()-$this->start;
			Log::info(sprintf('%s:- Closing [%s], sent in [%d] with [%s] items',self::LOGKEY,$this->sending->nameas,$end,$this->sending->dbids->count()));
		}

		$this->sending->close($successful,$node);
		$this->index = NULL;
	}

	public function dynamic(Address $ao): bool
	{
		$file = FALSE;

		// If the node is marked as hold - dont send any files.
		if ($ao->system->hold) {
			Log::info(sprintf('%s:- System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id));

			return FALSE;
		}

		// Files
		if (($x=$ao->dynamicWaiting())->count()) {
			Log::debug(sprintf('%s:- [%d] Dynamic Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));

			// Add Files
			foreach ($x as $do)
				$this->list->push(new Dynamic($do,$ao,self::T_FILE));

			$file = TRUE;
		}

		return $file;
	}

	/*
	private function compress(string $comp_mode): void
	{
		switch ($comp_mode) {
			case 'BZ2':
				$this->comp_data = bzcompress($buf);
				break;

			case 'GZ':
				$this->comp_data = gzcompress($buf);
				break;
		}
	}
	*/

	/**
	 * Check if we are at the end of the file
	 *
	 * @return bool
	 */
	public function feof(): bool
	{
		return $this->sending->feof();
	}

	/**
	 * Add our mail to the send queue
	 *
	 * @param Address $ao
	 * @return bool
	 * @throws Exception
	 */
	public function files(Address $ao): bool
	{
		$file = FALSE;

		// If the node is marked as hold - dont send any files.
		if ($ao->system->hold) {
			Log::info(sprintf('%s:- System [%d] is marked as hold - not checking for files.',self::LOGKEY,$ao->system_id));

			return FALSE;
		}

		// Files
		if (($x=$ao->filesWaiting())->count()) {
			Log::info(sprintf('%s:- [%d] Files(s) added for sending to [%s]',self::LOGKEY,$x->count(),$ao->ftn));

			// Add Files
			foreach ($x as $fo) {
				$this->list->push(new File($fo,self::T_FILE));
				$this->list->push(new Tic($fo,$ao,self::T_NONE));
			}

			$file = TRUE;
		}

		return $file;
	}

	/**
	 * Open a file for sending
	 *
	 * @param string $compress
	 * @return bool
	 * @throws Exception
	 */
	public function open(string $compress=''): bool
	{
		Log::debug(sprintf('%s:+ File Open to send',self::LOGKEY));

		if ((($this->index=$this->list->search(function($item) { return $item->complete === FALSE; })) !== FALSE)
			&& $this->sending->open())
		{
			Log::info(sprintf('%s:- Content Send item [#%d] (%s) with size [%d]',self::LOGKEY,$this->index,$this->sending->nameas,$this->sending->size));

			$this->pos = 0;
			$this->start = time();

			/*
			if ($compress)
				$this->comp_data = $this->compdata($compress);
			*/

			return TRUE;

		} else {
			throw new Exception('No files to open');
		}
	}

	/**
	 * Add our mail to the send queue
	 *
	 * @param Address $ao
	 * @return bool
	 * @throws Exception
	 */
	public function mail(Address $ao): bool
	{
		$mail = FALSE;

		// If the node is marked as hold - dont send any mail.
		if ($ao->system->hold) {
			Log::info(sprintf('%s:- System [%d] is marked as hold - not checking for mail.',self::LOGKEY,$ao->system_id));

			return FALSE;
		}

		// Netmail
		if ($x=$ao->getNetmail()) {
			Log::debug(sprintf('%s:- Netmail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));

			$this->list->push(new Mail($x,self::T_NETMAIL));
			$mail = TRUE;
		}

		// Echomail
		if ($x=$ao->getEchomail()) {
			Log::debug(sprintf('%s:- Echomail(s) added for sending to [%s]',self::LOGKEY,$ao->ftn));

			$this->list->push(new Mail($x,self::T_ECHOMAIL));
			$mail = TRUE;
		}

		return $mail;
	}

	/**
	 * Read bytes of the sending file
	 *
	 * @param int $length
	 * @return string|null
	 * @throws UnreadableFileEncountered
	 * @throws Exception
	 */
	public function read(int $length): ?string
	{
		if (! $this->fd)
			throw new Exception('No file open for read');

		$data = $this->sending->read($length);

		if ($data === FALSE)
			throw new UnreadableFileEncountered('Error reading file: '.$this->sending->name);

		$this->pos += strlen($data);

		Log::debug(sprintf('%s:- Content Read [%d] bytes, pos now [%d]',self::LOGKEY,strlen($data),$this->pos));

		return $data;
	}

	/**
	 * Seek to a specific position of our file
	 *
	 * @param int $pos
	 * @return bool
	 * @throws Exception
	 */
	public function seek(int $pos): bool
	{
		if (! $this->fd)
			throw new Exception('No file open for seek');

		if ($this->sending->seek($pos)) {
			$this->pos = $pos;

			Log::debug(sprintf('%s:= Content Seek to [%d]',self::LOGKEY,$this->pos));

			return TRUE;

		} else {
			Log::error(sprintf('%s:! Failed to seek to [%d]',self::LOGKEY,$pos));

			return FALSE;
		}
	}
}