lib/barlix/code39.ex

defmodule Barlix.Code39 do
  alias Barlix.Utils

  @moduledoc """
  This module implements the [Code
  39](https://en.wikipedia.org/wiki/Code_39) symbology.
  """

  @doc """
  Encodes the given value using code 39 symbology. Only a subset of
  ascii characters are supported.

  ## Options

  * `:checksum` (boolean) - enables checksum. Defaults to `false`
  """
  @spec encode(String.t() | charlist, Keyword.t()) :: {:error, binary} | {:ok, Barlix.code()}
  def encode(value, options \\ []) do
    Utils.normalize_string(value)
    |> loop(Keyword.get(options, :checksum, false))
  end

  @doc """
  Accepts the same arguments as `encode/2`. Returns `t:Barlix.code/0` or
  raises `Barlix.Error` in case of invalid value.
  """
  @spec encode!(String.t() | charlist, Keyword.t()) :: Barlix.code() | no_return
  def encode!(value, options \\ []) do
    case encode(value, options) do
      {:ok, code} -> code
      {:error, error} -> raise Barlix.Error, error
    end
  end

  defp loop(value, use_checksum) do
    with {:ok, c} <-
           (if use_checksum do
              checksum(value, 0)
            else
              {:ok, []}
            end),
         {:ok, encoded} <- encodings(value, start_symbol()),
         encoded = [[encoded | c] | [0 | stop_symbol()]],
         do: {:ok, {:D1, Utils.flatten(encoded)}}
  end

  defp checksum([], acc) do
    c =
      rem(acc, 43)
      |> index_to_char
      |> encoding

    {:ok, [0 | c]}
  end

  defp checksum([h | t], acc) do
    with i when is_number(i) <- char_to_index(h),
         do: checksum(t, acc + i)
  end

  defp encodings([], acc), do: {:ok, acc}

  defp encodings([h | t], acc) do
    with e when is_list(e) <- encoding(h),
         do: encodings(t, [acc | [0 | e]])
  end

  defp encoding(?0), do: [1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]
  defp encoding(?1), do: [1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1]
  defp encoding(?2), do: [1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1]
  defp encoding(?3), do: [1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1]
  defp encoding(?4), do: [1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1]
  defp encoding(?5), do: [1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1]
  defp encoding(?6), do: [1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1]
  defp encoding(?7), do: [1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]
  defp encoding(?8), do: [1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1]
  defp encoding(?9), do: [1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1]
  defp encoding(?A), do: [1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1]
  defp encoding(?B), do: [1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1]
  defp encoding(?C), do: [1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1]
  defp encoding(?D), do: [1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1]
  defp encoding(?E), do: [1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1]
  defp encoding(?F), do: [1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1]
  defp encoding(?G), do: [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1]
  defp encoding(?H), do: [1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1]
  defp encoding(?I), do: [1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1]
  defp encoding(?J), do: [1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1]
  defp encoding(?K), do: [1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1]
  defp encoding(?L), do: [1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1]
  defp encoding(?M), do: [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1]
  defp encoding(?N), do: [1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1]
  defp encoding(?O), do: [1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1]
  defp encoding(?P), do: [1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1]
  defp encoding(?Q), do: [1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1]
  defp encoding(?R), do: [1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1]
  defp encoding(?S), do: [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1]
  defp encoding(?T), do: [1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1]
  defp encoding(?U), do: [1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1]
  defp encoding(?V), do: [1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1]
  defp encoding(?W), do: [1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1]
  defp encoding(?X), do: [1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1]
  defp encoding(?Y), do: [1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1]
  defp encoding(?Z), do: [1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1]
  defp encoding(?-), do: [1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1]
  defp encoding(?.), do: [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1]
  defp encoding(?\s), do: [1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
  defp encoding(?$), do: [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1]
  defp encoding(?/), do: [1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1]
  defp encoding(?+), do: [1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1]
  defp encoding(?%), do: [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]

  defp encoding(invalid),
    do: {:error, "Invalid character found #{IO.chardata_to_string([invalid])}"}

  defp start_symbol, do: [1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1]
  defp stop_symbol, do: start_symbol()

  defp char_to_index(?0), do: 0
  defp char_to_index(?1), do: 1
  defp char_to_index(?2), do: 2
  defp char_to_index(?3), do: 3
  defp char_to_index(?4), do: 4
  defp char_to_index(?5), do: 5
  defp char_to_index(?6), do: 6
  defp char_to_index(?7), do: 7
  defp char_to_index(?8), do: 8
  defp char_to_index(?9), do: 9
  defp char_to_index(?A), do: 10
  defp char_to_index(?B), do: 11
  defp char_to_index(?C), do: 12
  defp char_to_index(?D), do: 13
  defp char_to_index(?E), do: 14
  defp char_to_index(?F), do: 15
  defp char_to_index(?G), do: 16
  defp char_to_index(?H), do: 17
  defp char_to_index(?I), do: 18
  defp char_to_index(?J), do: 19
  defp char_to_index(?K), do: 20
  defp char_to_index(?L), do: 21
  defp char_to_index(?M), do: 22
  defp char_to_index(?N), do: 23
  defp char_to_index(?O), do: 24
  defp char_to_index(?P), do: 25
  defp char_to_index(?Q), do: 26
  defp char_to_index(?R), do: 27
  defp char_to_index(?S), do: 28
  defp char_to_index(?T), do: 29
  defp char_to_index(?U), do: 30
  defp char_to_index(?V), do: 31
  defp char_to_index(?W), do: 32
  defp char_to_index(?X), do: 33
  defp char_to_index(?Y), do: 34
  defp char_to_index(?Z), do: 35
  defp char_to_index(?-), do: 36
  defp char_to_index(?.), do: 37
  defp char_to_index(?\s), do: 38
  defp char_to_index(?$), do: 39
  defp char_to_index(?/), do: 40
  defp char_to_index(?+), do: 41
  defp char_to_index(?%), do: 42

  defp char_to_index(invalid),
    do: {:error, "Invalid character found #{IO.chardata_to_string([invalid])}"}

  defp index_to_char(0), do: ?0
  defp index_to_char(1), do: ?1
  defp index_to_char(2), do: ?2
  defp index_to_char(3), do: ?3
  defp index_to_char(4), do: ?4
  defp index_to_char(5), do: ?5
  defp index_to_char(6), do: ?6
  defp index_to_char(7), do: ?7
  defp index_to_char(8), do: ?8
  defp index_to_char(9), do: ?9
  defp index_to_char(10), do: ?A
  defp index_to_char(11), do: ?B
  defp index_to_char(12), do: ?C
  defp index_to_char(13), do: ?D
  defp index_to_char(14), do: ?E
  defp index_to_char(15), do: ?F
  defp index_to_char(16), do: ?G
  defp index_to_char(17), do: ?H
  defp index_to_char(18), do: ?I
  defp index_to_char(19), do: ?J
  defp index_to_char(20), do: ?K
  defp index_to_char(21), do: ?L
  defp index_to_char(22), do: ?M
  defp index_to_char(23), do: ?N
  defp index_to_char(24), do: ?O
  defp index_to_char(25), do: ?P
  defp index_to_char(26), do: ?Q
  defp index_to_char(27), do: ?R
  defp index_to_char(28), do: ?S
  defp index_to_char(29), do: ?T
  defp index_to_char(30), do: ?U
  defp index_to_char(31), do: ?V
  defp index_to_char(32), do: ?W
  defp index_to_char(33), do: ?X
  defp index_to_char(34), do: ?Y
  defp index_to_char(35), do: ?Z
  defp index_to_char(36), do: ?-
  defp index_to_char(37), do: ?.
  defp index_to_char(38), do: ?\s
  defp index_to_char(39), do: ?$
  defp index_to_char(40), do: ?/
  defp index_to_char(41), do: ?+
  defp index_to_char(42), do: ?%
end