<?php

namespace App\Media\QuickTime;

use Illuminate\Support\Collection;

use App\Media\QuickTime\Atoms\moov\{mvhd,trak};
use App\Traits\FindQuicktimeAtoms;

abstract class Atom
{
	use FindQuicktimeAtoms;

	protected const record_size = 16384;

	protected const BLOCK_SIZE = 4096;

	protected int $offset;
	protected int $size;
	protected string $filename;

	private mixed $fh;
	private int $fp;
	protected Collection $cache;
	protected Collection $atoms;

	public function __construct(int $offset,int $size,string $filename)
	{
		$this->offset = $offset;

		// Quick validation
		if ($size < 0)
			throw new \Exception(sprintf('Atom cannot be negative. (%d)',$size));

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

	public function __get(string $key): mixed
	{
		switch ($key) {
			// Create time is in the MOOV/MVHD atom
			case 'creation_date':
			case 'duration':
			case 'preferred_rate':
			case 'preferred_volume':
				$subatom = $this->find_atoms(mvhd::class,1);

				return $subatom->{$key};

			// Height is in the moov/trak/tkhd attom
			case 'height':
			// Width is in the moov/trak/tkhd attom
			case 'width':
				$atom = $this->find_atoms(trak::class);

				return $atom->map(fn($item)=>$item->{$key})->filter()->max();

			// Signatures are calculated by the sha of the MDAT atom.
			case 'signature':
				return $this->signature();

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

	protected function data(): string
	{
		// Quick validation
		if ($this->size > self::record_size)
			throw new \Exception(sprintf('Refusing to read more than %d of data',self::record_size));

		$data = '';
		if ($this->fopen()) {
			while (! is_null($read=$this->fread()))
				$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 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();
	}
}