defmodule Tds.Query do
  @moduledoc """
  TDS query encoding and decoding
  """

  @type t :: %__MODULE__{
          statement: String.t(),
          handle: term
        }

  defstruct [:statement, :handle]
end

defimpl DBConnection.Query, for: Tds.Query do
  alias Tds.Parameter
  alias Tds.Types
  alias Tds.Query
  alias Tds.Result

  def encode(_statement, [], _opts) do
    []
  end

  def encode(%Query{handle: nil}, params, _) do
    params
  end

  def encode(%Query{statement: statement}, params, _) do
    param_desc = Enum.map_join(params, ", ", &Types.encode_param_descriptor/1)

    [
      %Parameter{value: statement, type: :string},
      %Parameter{value: param_desc, type: :string}
    ] ++ params
  end

  def decode(_query, [], _opts), do: []

  def decode(query, [h | t], opts) do
    [decode(query, h, opts) | decode(query, t, opts)]
  end

  def decode(_query, %Result{rows: nil} = result, _opts), do: result

  def decode(_query, %Result{rows: rows} = result, opts) do
    mapper = opts[:decode_mapper] || fn x -> x end
    rows = do_decode(rows, mapper, [])
    %Result{result | rows: rows}
  end

  def do_decode([row | rows], mapper, decoded) do
    decoded = [mapper.(row) | decoded]
    do_decode(rows, mapper, decoded)
  end

  def do_decode(_, _, decoded) do
    decoded
  end

  def parse(params, _) do
    params
  end

  def describe(query, _) do
    query
  end
end

defimpl String.Chars, for: Tds.Query do
  @spec to_string(Tds.Query.t()) :: String.t()
  def to_string(%Tds.Query{statement: statement}) do
    IO.iodata_to_binary(statement)
  end
end
