defmodule GuardedStructTest.Support.UUID do
  @moduledoc """
  **To make the UUID, this module is completely copied from the ecto project under the Apache License 2.0.**
  **For more information and more functions, please refer to the following path:**

  - https://github.com/elixir-ecto/ecto/blob/v3.11.2/lib/ecto/uuid.ex#L1
  - https://hexdocs.pm/ecto/Ecto.UUID.html

  > It should be noted that this file only supports uuid version 4.
  > To use other versions in hex, look for another library
  """

  @type t :: <<_::288>>
  @type raw :: <<_::128>>

  defmodule HelperEcto.CastError do
    @moduledoc """
    Raised when a changeset can't cast a value.
    """
    defexception [:message, :type, :value]

    def exception(opts) do
      type = Keyword.fetch!(opts, :type)
      value = Keyword.fetch!(opts, :value)
      msg = opts[:message] || "cannot cast #{inspect(value)} to #{format(type)}"
      %__MODULE__{message: msg, type: type, value: value}
    end

    @doc """
    Format type for error messaging and logs.
    """
    def format({composite, type}) when composite in [:array, :map] do
      "{#{inspect(composite)}, #{format(type)}}"
    end

    def format({:parameterized, type, params}) do
      if function_exported?(type, :format, 1) do
        apply(type, :format, [params])
      else
        "##{inspect(type)}<#{inspect(params)}>"
      end
    end

    def format(type), do: inspect(type)
  end

  @spec cast(t | raw | any) :: {:ok, t} | :error
  def cast(uuid)

  def cast(
        <<a1, a2, a3, a4, a5, a6, a7, a8, ?-, b1, b2, b3, b4, ?-, c1, c2, c3, c4, ?-, d1, d2, d3,
          d4, ?-, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>
      ) do
    <<c(a1), c(a2), c(a3), c(a4), c(a5), c(a6), c(a7), c(a8), ?-, c(b1), c(b2), c(b3), c(b4), ?-,
      c(c1), c(c2), c(c3), c(c4), ?-, c(d1), c(d2), c(d3), c(d4), ?-, c(e1), c(e2), c(e3), c(e4),
      c(e5), c(e6), c(e7), c(e8), c(e9), c(e10), c(e11), c(e12)>>
  catch
    :error -> :error
  else
    hex_uuid -> {:ok, hex_uuid}
  end

  def cast(<<_::128>> = raw_uuid), do: {:ok, encode(raw_uuid)}
  def cast(_), do: :error

  @spec cast!(t | raw | any) :: t
  def cast!(uuid) do
    case cast(uuid) do
      {:ok, hex_uuid} -> hex_uuid
      :error -> raise HelperEcto.CastError, type: __MODULE__, value: uuid
    end
  end

  @compile {:inline, c: 1}

  defp c(?0), do: ?0
  defp c(?1), do: ?1
  defp c(?2), do: ?2
  defp c(?3), do: ?3
  defp c(?4), do: ?4
  defp c(?5), do: ?5
  defp c(?6), do: ?6
  defp c(?7), do: ?7
  defp c(?8), do: ?8
  defp c(?9), do: ?9
  defp c(?A), do: ?a
  defp c(?B), do: ?b
  defp c(?C), do: ?c
  defp c(?D), do: ?d
  defp c(?E), do: ?e
  defp c(?F), do: ?f
  defp c(?a), do: ?a
  defp c(?b), do: ?b
  defp c(?c), do: ?c
  defp c(?d), do: ?d
  defp c(?e), do: ?e
  defp c(?f), do: ?f
  defp c(_), do: throw(:error)

  @spec generate() :: t
  def generate(), do: encode(bingenerate())

  @spec bingenerate() :: raw
  def bingenerate() do
    <<u0::48, _::4, u1::12, _::2, u2::62>> = :crypto.strong_rand_bytes(16)
    <<u0::48, 4::4, u1::12, 2::2, u2::62>>
  end

  @doc false
  def autogenerate, do: generate()

  @spec encode(raw) :: t
  defp encode(
         <<a1::4, a2::4, a3::4, a4::4, a5::4, a6::4, a7::4, a8::4, b1::4, b2::4, b3::4, b4::4,
           c1::4, c2::4, c3::4, c4::4, d1::4, d2::4, d3::4, d4::4, e1::4, e2::4, e3::4, e4::4,
           e5::4, e6::4, e7::4, e8::4, e9::4, e10::4, e11::4, e12::4>>
       ) do
    <<e(a1), e(a2), e(a3), e(a4), e(a5), e(a6), e(a7), e(a8), ?-, e(b1), e(b2), e(b3), e(b4), ?-,
      e(c1), e(c2), e(c3), e(c4), ?-, e(d1), e(d2), e(d3), e(d4), ?-, e(e1), e(e2), e(e3), e(e4),
      e(e5), e(e6), e(e7), e(e8), e(e9), e(e10), e(e11), e(e12)>>
  end

  @compile {:inline, e: 1}

  defp e(0), do: ?0
  defp e(1), do: ?1
  defp e(2), do: ?2
  defp e(3), do: ?3
  defp e(4), do: ?4
  defp e(5), do: ?5
  defp e(6), do: ?6
  defp e(7), do: ?7
  defp e(8), do: ?8
  defp e(9), do: ?9
  defp e(10), do: ?a
  defp e(11), do: ?b
  defp e(12), do: ?c
  defp e(13), do: ?d
  defp e(14), do: ?e
  defp e(15), do: ?f
end
