<?php

namespace App\Media;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;

abstract class Base
{
	protected const record_size = 16384;	// Limiting the maximum amount of data we read into memory

	/** Full path to the file */
	protected string $filename;
	protected int $filesize;
	protected ?string $type;

	private mixed $fh;
	private int $fp;

	protected Collection $cache;
	protected ?string $unused_data;

	public function __construct(string $filename,string $type)
	{
		Log::info(sprintf('Create a media type [%s] for [%s]',get_class($this),$filename));

		$this->filename = $filename;
		$this->filesize = filesize($filename);
		$this->type = $type;
		$this->cache = collect();
	}

	/**
	 * Enable getting values for keys in the response
	 *
	 * @param string $key
	 * @return mixed|object
	 * @throws \Exception
	 */
	public function __get(string $key): mixed
	{
		switch ($key) {
			case 'fh':
			case 'type':
				return $this->{$key};

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

	/**
	 * Unpack data into our cache
	 *
	 * @param string|null $data
	 * @return Collection
	 * @throws \Exception
	 */
	protected function cache(?string $data=NULL): Collection
	{
		$data = $data ?: $this->data();

		if (! count($this->cache) && $this->size) {
			$this->cache = collect(unpack($this->unpack(),$data));

			if ($this->size > ($x=$this->unpack_size()))
				$this->unused_data = substr($data,$x);
		}

		return $this->cache;
	}

	protected function data(int $size=4096): string
	{
		// Quick validation
		if (($size ?: $this->size) > static::record_size)
			throw new \Exception(sprintf('Refusing to read [%d] which is more than %d of data',$size ?: $this->size,self::record_size));

		$data = '';
		if ($this->fopen()) {
			while ((! is_null($read=$this->fread())) && (strlen($data) <= ($size ?: $this->size)))
				$data .= $read;

			$this->fclose();
		}

		return $data;
	}

	protected function fclose(): bool
	{
		fclose($this->fh);
		unset($this->fh);

		return TRUE;
	}

	/**
	 * Open the file and seek to the atom
	 *
	 * @return bool
	 */
	protected function fopen(): bool
	{
		$this->fh = fopen($this->filename,'r');
		fseek($this->fh,$this->offset);
		$this->fp = 0;

		return TRUE;
	}

	/**
	 * Read the atom from the file
	 *
	 * @param int $size
	 * @return string|NULL
	 */
	protected function fread(int $size=4096): ?string
	{
		if ($this->fp === $this->size)
			return NULL;

		if ($this->fp+$size > $this->size)
			$size = $this->size-$this->fp;

		$read = fread($this->fh,$size);
		$this->fp += $size;

		return $read;
	}

	protected function fseek(int $offset): int
	{
		$this->fp = $offset;

		return fseek($this->fh,$this->offset+$this->fp);
	}

	protected function ftell(): int
	{
		return ftell($this->fh);
	}

	protected function unpack(array $unpack=[]): string
	{
		return collect($unpack ?: static::unpack)->map(fn($v,$k)=>$v[0].$k)->join('/');
	}

	protected function unpack_size(array $unpack=[]): int
	{
		return collect($unpack ?: static::unpack)->map(fn($v,$k)=>$v[1])->sum();
	}
}