<?php

namespace App\Media\QuickTime;

use Illuminate\Support\Collection;

trait FindQuicktimeAtoms
{
	protected function get_atoms(string $class_prefix,string $unknown,int $offset,int $size,string $atom=NULL,string $passthru=NULL,\Closure $callback=NULL): Collection
	{
		// List of atoms
		// File Type atom should proceed move, movie data, preview. free space atoms, if it exists.
		// Can assume it doesnt exist if those other atoms are discovered first.
		// Atoms can be present in any order
		// Must contain a movie atom.
		// Movie data atoms can exceed 2^32.

		$rp = 0;
		if (! $atom) {
			$fh = fopen($this->filename,'r');
			fseek($fh,$offset);
		}

		$result = collect();

		$prelude = 8;
		while ($rp < $size) {
			$read = $atom ? substr($atom,$rp,$prelude) : fread($fh,$prelude);
			$header = unpack('Nsize/a4atom',$read);
			$rp += strlen($read);

			// For mdat atoms, if size = 1, the true size is in the 64 bit extended header, in the next 8 bytes
			if (($header['atom'] === 'mdat') && ($header['size'] === 1)) {
				$eheader = unpack('Jsize',fread($fh,8));
				$rp += 8;
				$prelude = 16;

				$header['size'] = $eheader['size'];
			}

			// Load our class for this supplier
			$class = $class_prefix.strtolower($header['atom']);

			$data = $atom
				? substr($atom,$rp,$header['size']-$prelude)
				: ($header['size']-$prelude && ($header['size']-$prelude <= self::record_size) ? fread($fh,$header['size']-$prelude) : NULL);

			if ($header['size'] >= $prelude) {
				$o = class_exists($class)
					? new $class($offset+$rp,$header['size']-$prelude,$this->filename,$data,$passthru)
					: new $unknown($offset+$rp,$header['size']-$prelude,$this->filename,$header['atom'],$data);

				$result->push($o);

				$rp += $header['size']-$prelude;

				// Only need to seek if we didnt read all the data
				if ((! $atom) && ($header['size']-8 > static::record_size))
					fseek($fh,$offset+$rp);

			} else {
				dd([get_class($this) => $data]);
			}

			// Work out if data from the last atom next to be passed onto the next one
			if ($callback)
				$passthru = $callback($o);
		}

		if (! $atom) {
			fclose($fh);
			unset($fh);
		}

		return $result;
	}

	/**
	 * Recursively look through our object hierarchy of atoms for a specific atom class
	 *
	 * @param string $subatom
	 * @param int|NULL $expect
	 * @param int $depth
	 * @return Collection|Atom|NULL
	 * @throws \Exception
	 */
	public function find_atoms(string $subatom,?int $expect=NULL,int $depth=100): Collection|Atom|NULL
	{
		if (! isset($this->atoms) || ($depth < 0))
			return NULL;

		$subatomo = $this->atoms->filter(fn($item)=>get_class($item)===$subatom);

		$subatomo = $subatomo
			->merge($this->atoms->map(fn($item)=>$item->find_atoms($subatom,NULL,$depth-1))
			->filter(fn($item)=>$item ? $item->count() : NULL)
			->flatten());

		if (! $subatomo->count())
			return $subatomo;

		if ($expect && ($subatomo->count() !== $expect))
			throw new \Exception(sprintf('! Expected %d subatoms of %s, but have %d',$expect,$subatom,$subatomo->count()));

		return ($expect === 1) ? $subatomo->pop() : $subatomo;
	}
}