lib/tx_build/predicate.ex

defmodule Stellar.TxBuild.Claimants do
end

# ClaimPredicate
# ----
# Stellar.TxBuild.Claimant.new(destination: "ABC", predicate: :unconditional)
# Stellar.TxBuild.Claimant.new(destination: "ABC", predicate: conditional: [time: {:relative, 123}])
#
defmodule Stellar.TxBuild.UnconditionalPredicate do
  @moduledoc """
  """

  @behaviour Stellar.TxBuild.XDR
end

defmodule Stellar.TxBuild.ConditionalPredicate do
  @moduledoc """
  """

  @behaviour Stellar.TxBuild.XDR
end

defmodule Stellar.TxBuild.Predicate do
  @moduledoc """
  `Predicate` struct definition.
  """

  @type predicate :: :conditional | :unconditional
  @type type :: :time | :and | :or | :not

  @type t :: %__MODULE__{predicate: predicate(), type: type(), value: any()}

  defstruct [:predicate, :type, :value]

  def new(:unconditional), do: %__MODULE__{type: :unconditional}

  def new(conditional: predicate) do
    with {:ok, {type, value}} <- validate_predicate(predicate) do
      %__MODULE__{type: :time, value: value}
    end
  end

  def new(_predicate), do: {:error, :invalid_predicate}

  defp validate_predicate(time: {type, value}) when type in ~w(relative absolute)a and is_integer(value) do
    {:ok, {:time, {type, value}}}
  end

  defp validate_predicate(and: {cond1, cond2}) do
    with {:ok, predicate1} <- new(cond1),
      {:ok, predicate2} <- new(cond2) do
      {:ok, {:and, {predicate1, predicate2}}}
    end
  end

  defp validate_predicate(or: {cond1, cond2}) do
    with {:ok, predicate1} <- new(cond1),
      {:ok, predicate2} <- new(cond2) do
      {:ok, {:or, {predicate1, predicate2}}}
    end
  end

  defp validate_predicate(not: conditional) do
    {:ok, {:and, new(conditional)}}
  end
end


defmodule Stellar.TxBuild.Claimant do
  @moduledoc """
  """
  @behaviour Stellar.TxBuild.XDR

  alias Stellar.TxBuild.{AccountID, Predicate}

  @type account_id :: String.t()
  @type validation :: {:ok, any()} | {:error, atom()}

  @type t :: %__MODULE__{destination: account_id(), predicate: Predicate.t()}

  defstruct [:destination, :predicate]

  def new(args, opts \\ [])

  def new(args, _opts) do
    destination = Keyword.get(args, :destination)
    predicate = Keyword.get(args, :predicate)

    with {:ok, destination} <- validate_destionation(destination),
         {:ok, predicate} <- validate_predicate(predicate) do
      %__MODULE__{destination: destination, predicate: predicate}
    end
  end

  @spec validate_destionation(account_id :: account_id()) :: validation()
  defp validate_destionation(account_id) do
    case AccountID.new(account_id) do
      %AccountID{} = account_id -> {:ok, account_id}
      _error -> {:error, :invalid_destination}
    end
  end

  @spec validate_predicate(predicate :: any()) :: validation()
  defp validate_predicate(predicate) do
    case Predicate.new(predicate) do
      %Predicate{} = predicate -> {:ok, predicate}
      _error -> {:error, :invalid_predicate}
    end
  end

  # defp validate_predicate({:unconditional, _predicate}), do: validate_predicate(:unconditional)
  # defp validate_predicate(:unconditional), do: :unconditional
  # defp validate_predicate(_predicate), do: {:error, :invalid_predicate}
end