defmodule Membrane.H26x.ExpGolombConverter do
  @moduledoc """
  This module holds function responsible for converting
  from and to Exp-Golomb Notation.

  @doc """
  Reads the appropriate number of bits from the bitstring and decodes an
  integer out of these bits.

  Returns the decoded integer and the rest of the bitstring, which wasn't
  used for decoding. By default, the decoded integer is an unsigned integer.
  If `negatives: true` is passed as an option, the decoded integer will be
  a signed integer.
  @spec to_integer(bitstring(), keyword()) :: {integer(), bitstring()}
  def to_integer(binary, opts \\ [negatives: false])

  def to_integer(binary, negatives: should_support_negatives) do
    zeros_size = cut_zeros(binary)
    number_size = zeros_size + 1
    <<_zeros::size(zeros_size), number::size(number_size), rest::bitstring>> = binary
    number = number - 1

    if should_support_negatives do
      if rem(number, 2) == 0, do: {-div(number, 2), rest}, else: {div(number + 1, 2), rest}
      {number, rest}

  @doc """
  Returns a bitstring with an Exponential Golomb representation of a integer.

  By default, the function expects the number to be a non-negative integer.
  If `negatives: true` option is set, the function can also encode negative
  numbers, but number encoded with `negatives: true` option also needs to be
  decoded with that option.
  @spec to_bitstring(integer(), negatives: boolean()) :: bitstring()
  def to_bitstring(integer, opts \\ [negatives: false])

  def to_bitstring(integer, negatives: false) do
    # ceil(log(x)) can be calculated more accuratly and efficiently
    number_size = trunc(:math.floor(:math.log2(integer + 1))) + 1
    zeros_size = number_size - 1
    <<0::size(zeros_size), integer + 1::size(number_size)>>

  def to_bitstring(integer, negatives: true) do
    if integer < 0,
      do: to_bitstring(-2 * integer, negatives: false),
      else: to_bitstring(2 * integer - 1, negatives: false)

  defp cut_zeros(bitstring, how_many_zeros \\ 0) do
    <<x::1, rest::bitstring>> = bitstring

    case x do
      0 -> cut_zeros(rest, how_many_zeros + 1)
      1 -> how_many_zeros