lib/bit_flagger.ex

defmodule BitFlagger do
  @moduledoc """
  A set of functions that manipulate bit flags.
  """

  use Bitwise, only_operators: true

  @type state :: non_neg_integer | binary
  @type index :: non_neg_integer

  @doc """
  Converts state to a list of boolean values.

  ## Examples

      iex> parse(0b1010, 4)
      [false, true, false, true]

      iex> parse(<<0b1010>>, 4)
      [false, true, false, true]

  """
  @spec parse(state, pos_integer) :: list(boolean)
  def parse(state, size) when (is_integer(state) or is_binary(state)) and is_integer(size) do
    for index <- 0..(size - 1), do: on?(state, index)
  end

  @doc """
  Checks if the flag is turned on at a specified index.

  ## Examples

      iex> on?(0b0010, 1)
      true

      iex> on?(0b0010, 3)
      false

      iex> on?(<<0b0010>>, 1)
      true

      iex> on?(<<0b0010>>, 3)
      false

  """
  @spec on?(state, index) :: boolean
  def on?(state, index) when is_integer(state) and is_integer(index) do
    (state >>> index &&& 0x01) == 1
  end

  def on?(state, index) when is_binary(state) do
    state
    |> :binary.decode_unsigned()
    |> on?(index)
  end

  @doc """
  Checks if the flag is turned off at a specified index.

  ## Examples

      iex> off?(0b0001, 1)
      true

      iex> off?(0b0001, 0)
      false

      iex> off?(<<0b0001>>, 1)
      true

      iex> off?(<<0b0001>>, 0)
      false

  """
  @spec off?(state, index) :: boolean
  def off?(state, index), do: !on?(state, index)

  @doc """
  Turns on the flag at a specified index.

  ## Examples

      iex> on(0b0000, 2)
      0b0100

      iex> on(<<0b0000>>, 2)
      <<0b0100>>

  """
  @spec on(state, index) :: state
  def on(state, index) when is_integer(state) and is_integer(index) do
    state ||| 0x01 <<< index
  end

  def on(state, index) when is_binary(state) do
    state
    |> :binary.decode_unsigned()
    |> on(index)
    |> :binary.encode_unsigned()
  end

  @doc """
  Turns off the flag at a specified index.

  ## Examples

      iex> off(0b1111, 2)
      0b1011

      iex> off(<<0b1111>>, 2)
      <<0b1011>>

  """
  @spec off(state, index) :: state
  def off(state, index) when is_integer(state) and is_integer(index) do
    state &&& ~~~(0x01 <<< index)
  end

  def off(state, index) when is_binary(state) do
    state
    |> :binary.decode_unsigned()
    |> off(index)
    |> :binary.encode_unsigned()
  end
end