<?php

namespace App\Classes\File;

use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\UnreadableFileEncountered;

use App\Models\File;

/**
 * A file we are sending or receiving
 *
 * @property string $name
 * @property string $recvas
 * @property int $size
 */
class Item
{
	private const LOGKEY = 'I--';

	// For deep debugging
	protected bool $DEBUG = FALSE;

	/** @var int max size of file to use compression */
	// @todo MAX_COMPSIZE hasnt been implemented in RECEIVE OR SEND
	protected const MAX_COMPSIZE	= 0x1fff;

	protected const IS_PKT			= (1<<1);
	protected const IS_ARC			= (1<<2);
	protected const IS_FILE			= (1<<3);
	protected const IS_FLO			= (1<<4);
	protected const IS_REQ			= (1<<5);
	protected const IS_TIC			= (1<<6);

	protected const I_RECV			= (1<<0);
	protected const I_SEND			= (1<<1);

	protected string $file_name = '';
	protected int $file_size = 0;
	protected int $file_mtime = 0;
	/** Current read/write pointer */
	protected int $file_pos = 0;
	/** File descriptor */
	protected mixed $f = NULL;
	protected int $type;
	protected int $action;
	protected File $filemodel;

	public bool $sent = FALSE;
	public bool $received = FALSE;
	public bool $incomplete = FALSE;
	/** Time we started sending/receiving */
	protected int $start;

	/**
	 * @throws FileNotFoundException
	 * @throws UnreadableFileEncountered
	 * @throws \Exception
	 */
	public function __construct($file,int $action)
	{
		switch ($action) {
			case self::I_SEND:
				if ($file instanceof File) {
					$this->filemodel = $file;
					// @todo We should catch any exceptions if the default storage is s3 (it is) and we cannot find the file, or the s3 call fails
					$this->file_size = Storage::size($file->full_storage_path);
					$this->file_mtime = Storage::lastModified($file->full_storage_path);

				} else {
					if (! is_string($file))
						throw new \Exception('Invalid object creation - file should be a string');

					if (! file_exists($file))
						throw new FileNotFoundException('Item doesnt exist: '.$file);

					if (! is_readable($file))
						throw new UnreadableFileEncountered('Item cannot be read: '.$file);

					$this->file_name = $file;
					$x = stat($file);
					$this->file_size = $x['size'];
					$this->file_mtime = $x['mtime'];
				}

				break;

			case self::I_RECV;
				$keys = ['name','mtime','size'];

				if (! is_array($file) || array_diff(array_keys($file),$keys))
					throw new \Exception('Invalid object creation - file is not a valid array :'.serialize(array_diff(array_keys($file),$keys)));

				$this->file_name = $file['name'];
				$this->file_size = $file['size'];
				$this->file_mtime = $file['mtime'];

				break;

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

		$this->action = $action;
		$this->type = $this->whatType();
	}

	/**
	 * @throws \Exception
	 */
	public function __get($key)
	{
		switch($key) {
			case 'mtime':
			case 'name':
			case 'size':
				if ($this->action & self::I_RECV|self::I_SEND)
					return $this->{'file_'.$key};

				throw new \Exception('Invalid request for key: '.$key);

			case 'recvas':
				return $this->file_name;

			case 'sendas':
				return $this->file_name ? basename($this->file_name) : $this->filemodel->name;

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

	protected function isType(int $type): bool
	{
		return $this->type & $type;
	}

	private function whatType(): int
	{
		static $ext = ['su','mo','tu','we','th','fr','sa','req'];

		$x = strrchr($this->file_name,'.');

		if (! $x || (strlen(substr($x,1)) != 3))
			return self::IS_FILE;

		if (strcasecmp(substr($x,2),'lo') === 0)
			return self::IS_FLO;

		if (strcasecmp(substr($x,1),'pkt') === 0)
			return self::IS_PKT;

		if (strcasecmp(substr($x,1),'req') === 0)
			return self::IS_REQ;

		if (strcasecmp(substr($x,1),'tic') === 0)
			return self::IS_TIC;

		for ($i=0;$i<count($ext);$i++)
			if (! strncasecmp($x,'.'.$ext[$i],strlen($ext[$i])) && (preg_match('/^[0-9a-z]/',strtolower(substr($x,3,1)))))
				return self::IS_ARC;

		return self::IS_FILE;
	}
}