lib/loop.ex

defmodule Y4mFrameLoop do
  defstruct [:file_path, :file, :reader]
  # @spec init(binary) :: %Y4mFrameLoop{
  @spec init(binary) :: {:error, atom} | %Y4mFrameLoop{}
  def init(file_path) do
    with {:ok, file} <- File.open(file_path),
         reader = Y4mReader.read(file) do
      %__MODULE__{file_path: file_path, file: file, reader: reader}
    end
  end

  def next(loop = %__MODULE__{file_path: path, file: file, reader: reader}) do
    case Y4mReader.next_frame(reader) do
      [y, u, v] -> {loop, [y, u, v]}

      :eof ->
        :ok = File.close(file)
        {:ok, file} = File.open(path)
        reader = Y4mReader.read(file)
        frame = Y4mReader.next_frame(reader)
        {%Y4mFrameLoop{reader: reader, file: file, file_path: path}, frame}
    end
  end
end

defimpl Enumerable, for: Y4mFrameLoop do
  def slice(_iter) do
    raise RuntimeError, "Y4mLoop only supports reduce"
  end

  def member?(_iter, _e) do
    raise RuntimeError, "Y4mLoop only supports reduce"
  end

  def count(_) do
    raise RuntimeError, "Y4mLoop only supports reduce"
  end

  def reduce(_loop, {:halt, acc}, _fun) do
    {:halted, acc}
  end

  def reduce(loop, {:suspend, acc}, fun) do
    {:suspended, acc, &reduce(loop, &1, fun)}
  end

  def reduce(loop = %Y4mFrameLoop{}, {:cont, acc}, fun) do
    {loop, frame} = Y4mFrameLoop.next(loop)
    reduce(loop, fun.(frame, acc), fun)
  end
end