lib/exiffer/jpeg/header/SOS.ex

defmodule Exiffer.JPEG.Header.SOS do
  @moduledoc """
  Documentation for `Exiffer.JPEG.Header.SOS`.
  """

  alias Exiffer.Buffer
  require Logger

  defstruct ~w(data)a

  defimpl Jason.Encoder  do
    @spec encode(%Exiffer.JPEG.Header.SOS{}, Jason.Encode.opts()) :: String.t()
    def encode(_entry, opts) do
      Jason.Encode.map(
        %{module: "Exiffer.JPEG.Header.SOS"},
        opts
      )
    end
  end

  def new(%{data: <<0xff, 0xda, _rest::binary>>} = buffer) do
    buffer = Buffer.skip(buffer, 2)
    {data, buffer} = read_data(<<>>, buffer)
    sos = %__MODULE__{data: data}
    {:ok, sos, buffer}
  end

  def binary(%__MODULE__{} = sos) do
    <<0xff, 0xda, sos.data::binary>>
  end

  def text(%__MODULE__{}) do
    """
    SOS
    ---
    """
  end

  def write(%__MODULE__{} = data, io_device) do
    Logger.debug "Writing SOS header"
    binary = binary(data)
    :ok = IO.binwrite(io_device, binary)
  end

  defp read_data(data, buffer) do
    case :binary.match(buffer.data, [<<0xff, 0xd9>>]) do
      {start, _length} ->
        {chunk, buffer} = Buffer.consume(buffer, start)
        data = <<data::binary, chunk::binary>>
        {data, buffer}
      :nomatch ->
        if buffer.status == :eof do
          {data, buffer}
        else
          {chunk, buffer} = Buffer.consume(buffer, buffer.remaining)
          data = <<data::binary, chunk::binary>>
          read_data(data, buffer)
        end
    end
  end

  defimpl Exiffer.Serialize do
    alias Exiffer.JPEG.Header.SOS

    def binary(data) do
      SOS.binary(data)
    end

    def text(data) do
      SOS.text(data)
    end

    def write(data, io_device) do
      SOS.write(data, io_device)
    end
  end
end