defmodule WaParser.LEB128 do
  def encode_unsigned(0) do
    <<0x00>>
  end

  def encode_unsigned(int) when int > 0 do
    int |> to_bitstring |> seven_bits_encoding
  end

  def decode_unsigned(binary) do
    decode_unsigned(binary, <<>>)
  end

  defp decode_unsigned(<<0::1, byte::7>>, acc) do
    bitstring = <<byte::7, acc::bitstring>>
    size = :erlang.bit_size(bitstring)
    <<int::unsigned-integer-size(size)>> = bitstring
    int
  end

  defp decode_unsigned(<<1::1, byte::7, rest::bytes>>, acc) do
    decode_unsigned(rest, <<byte::7, acc::bitstring>>)
  end

  def encode_signed(0) do
    <<0x00>>
  end

  def encode_signed(int) do
    int |> to_bitstring |> seven_bits_encoding
  end

  defp to_bitstring(int) do
    abs =
      if int < 0 do
        int * -1
      else
        int
      end

    bytes_needed = ceil(:math.log2(abs))
    bytes_needed = ceil(bytes_needed / 7) * 7
    <<int::size(bytes_needed)>>
  end

  defp seven_bits_encoding(binary) do
    seven_bits_encoding(binary, [])
  end

  defp seven_bits_encoding(<<>>, acc) do
    acc
    |> Enum.into(<<>>)
  end

  defp seven_bits_encoding(<<sb::size(7), rest::bitstring>>, acc) do
    byte =
      case acc do
        [] -> <<sb>>
        _ -> <<1::1, sb::7>>
      end

    seven_bits_encoding(rest, [byte | acc])
  end

  def decode_signed(binary) do
    decode_signed(binary, <<>>)
  end

  defp decode_signed(<<0::1, byte::7>>, acc) do
    bitstring = <<byte::7, acc::bitstring>>
    size = :erlang.bit_size(bitstring)
    <<int::signed-integer-size(size)>> = bitstring
    int
  end

  defp decode_signed(<<1::1, byte::7, rest::bytes>>, acc) do
    decode_signed(rest, <<byte::7, acc::bitstring>>)
  end
end