lib/arke/core/parameter.ex

# Copyright 2023 Arkemis S.r.l.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule Arke.Core.Parameter do
  @moduledoc """
  Module to manage the defaults parameter

  In order to create a new one simply use the .new(opts)

  ## Types
    - `string`
    - `integer`
    - `float`
    - `boolean`
    - `dict`
    - `date`
    - `time`
    - `datetime`

  ## Values
  There are two possible ways to declare the values for a parameter:
    - list => by giving a list of values \n
          values: ["value 1", "value 2", ...."value n"]
    - list of map => by giving a list of map. Each map **must** contain `label` and `value`. \n
          values: [%{label:"value 1", value: 1},... %{label: "value 999", value: 999}]

    The result will always be a list of map containing `label` and `value`. Keep in mind that if the values are provided using a list the `label` will be autogenerated.
    Remember also that all the values provided must be the same type as the [ParameterType](#module-types) and only `string`, `integer` and `float` support the `values` declaration

  ## Get list of attributes definable in opts during creation:
      iex> Arke.Core.Parameter.'ParameterType'.get_parameters()
  """
  alias Arke.Core.Parameter

  @type parameter_struct() ::
          Parameter.Boolean.t()
          | Parameter.String.t()
          | Parameter.Dict.t()
          | Parameter.Integer.t()
          | Parameter.Float.t()

  @doc """
       Macro defining a shared struct of parameter used across Arkes
       """ && false

  defmacro base_parameters() do
    quote do
      group(:parameter)

      parameter(:label, :string, required: true)
      parameter(:format, :string, default_string: "attribute")
      parameter(:is_primary, :boolean, default_boolean: false)
      parameter(:nullable, :boolean, default_boolean: true)
      parameter(:required, :boolean, default_boolean: false)
      parameter(:persistence, :string, default_string: "arke_parameter")
      parameter(:only_run_time, :boolean, default_boolean: false)
      parameter(:helper_text, :string, required: false)
    end
  end

  #  @doc """
  #  Create new parameter by passing the type and the options to assign
  #
  #  ## Parameters
  #    - opts => %{type: `ParameterType`, opts: [keyword: value] | %{map}} => opts like the `id` we want to give to the parameter we are creating.
  #      {[type](#module-types): `ParameterType`} is required unless you want to create a generic parameter with no type
  #
  #
  #  ## Examples
  #      iex> Arke.Core.Parameter.new(%{type: :string, opts: [id: "test"]})
  #
  #  ## Return
  #      %Arke.Core.Parameter.'ParameterType'{}
  #
  #  """
  #  @spec new(arg1 :: %{type: atom(), opts: list(parameter_struct())}) :: parameter_struct()
  #  def new(%{type: :string, opts: opts} = _), do: Parameter.String.new([{:type, :string} | opts])
  #  def new(%{type: :atom, opts: opts} = _), do: Parameter.String.new([{:type, :string} | opts])
  #  def new(%{type: :integer, opts: opts} = _), do: Parameter.Integer.new([{:type, :integer} | opts])
  #  def new(%{type: :float, opts: opts} = _), do: Parameter.Float.new([{:type, :float} | opts])
  #  def new(%{type: :boolean, opts: opts} = _), do: Parameter.Boolean.new([{:type, :boolean} | opts])
  #  def new(%{type: :date, opts: opts} = _), do: Parameter.Date.new([{:type, :date} | opts])
  #  def new(%{type: :time, opts: opts} = _), do: Parameter.Time.new([{:type, :time} | opts])
  #  def new(%{type: :datetime, opts: opts} = _), do: Parameter.DateTime.new([{:type, :datetime} | opts])
  #  def new(%{type: :dict, opts: opts} = _), do: Parameter.Dict.new([{:type, :dict} | opts])
  #  def new(%{opts: opts} = _), do: Enum.into(opts, %{helper_text: ""})
end

defmodule Arke.Core.Parameter.String do
  @moduledoc """
  Module that define the struct of a String by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.String

  ## Element added
    - `min_length` => :atom => define the min_length the string could have. It will check during creation
    - `max_length` => :atom => define the max_length the string could have. It will check during creation
    - `values` => [list] || [%{label: string, value: any}, ...] => use this to create a parameter with only certain values assignable. (Values must be the same type as the parameter we want to create)
    - `multiple` => boolean => relevant only if values are set. It makes possible to assign more than a values defined in values
    - `unique` => boolean => check if there is an existing record in the database  with the same value before creating one
    - `default` => String => default value

  ## Example
      iex> params = [id: :string_test, min_length: 1, values: ["value1", "value2"], multiple: true]
      ...> Arke.Core.Parameter.new(%{type: :string, opts: params})

  ## Return
      %Arke.Core.Parameter.String{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:min_length, :integer, required: false)
    parameter(:max_length, :integer, required: false)
    parameter(:strip, :boolean, default_boolean: false)
    parameter(:values, :list, required: false)
    parameter(:multiple, :boolean, default_boolean: false)
    parameter(:unique, :boolean, required: false)
    parameter(:default_string, :string, default_string: nil)
  end

  def before_load(data, _persistence_fn) do
    args = Arke.System.BaseParameter.check_enum(:string, Map.to_list(data))
    {:ok, Enum.into(args, %{})}
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Integer do
  @moduledoc """
  Module that define the struct of a Integer by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Integer

  ## Element added
    - `min` => :atom => define the mix value the parammeter could have
    - `max` => :atom => define the max the parammeter could have
    - `values` => [list] || [%{label: string, value: any}, ...] => use this to create a parameter with only certain values assignable. (Values must be the same type as the parameter we want to create)
    - `multiple` => boolean => relevant only if values are set. It makes possible to assign more than a values defined in values
    - `default` => Integer => default value

    ## Example
        iex> params = [id: :integer_test, min: 3, max: 7.5, values: [%{label: "option 1", value: 1}, %{label: "option 2", value: 2}]]
        ...> Arke.Core.Parameter.new(%{type: :integer, opts: params})

    ## Return
        %Arke.Core.Parameter.Float{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:min, :integer, required: false)
    parameter(:max, :integer, required: false)
    parameter(:values, :list, required: false)
    parameter(:multiple, :boolean, default_boolean: false)
    parameter(:default_integer, :integer, default_integer: nil)
  end

  def before_load(data, _persistence_fn) do
    args = Arke.System.BaseParameter.check_enum(:integer, Map.to_list(data))
    {:ok, Enum.into(args, %{})}
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Float do
  @moduledoc """
  Module that define the struct of a Float by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Float

  ## Element added
    - `min` => :atom => define the mix value the parammeter could have
    - `max` => :atom => define the max the parammeter could have
    - `values` => [list] || [%{label: string, value: any}, ...] => use this to create a parameter with only certain values assignable. (Values must be the same type as the parameter we want to create)
    - `multiple` => boolean => relevant only if values are set. It makes possible to assign more than a values defined in values
    - `default` => Float => default value

    ## Example
        iex> params = [id: :float_test, min: 3, max: 7.5]
        ...> Arke.Core.Parameter.new(%{type: :float, opts: params})

    ## Return
        %Arke.Core.Parameter.Float{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:min, :float, required: false)
    parameter(:max, :float, required: false)
    parameter(:values, :list, required: false)
    parameter(:multiple, :boolean, default_boolean: false)
    parameter(:default_float, :float, default_float: nil)
  end

  def before_load(data, _persistence_fn) do
    args = Arke.System.BaseParameter.check_enum(:float, Map.to_list(data))
    {:ok, Enum.into(args, %{})}
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Boolean do
  @moduledoc """
  Module that define the struct of a Boolean by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Boolean

  ## Element added
    - `default` => Boolean => default value

  ## Example
      iex> params = [id: :boolean_test, default: false]
      ...> Arke.Core.Parameter.Boolean(%{type: :boolean, opts: params})

  ## Return
      %Arke.Core.Parameter.Boolean{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:default_boolean, :boolean, default_boolean: false)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Dict do
  @moduledoc """
  Module that define the struct of a Dict by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Dict

  ## Element added
    - `default` => Dict => default value

  ## Example
      iex> params = [id: :dict_test, default: %{default_key: default_value}]
      ...> Arke.Core.Parameter.Dict(%{type: :dict, opts: params})

  ## Return
      %Arke.Core.Parameter.Dict{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:default_dict, :dict, default_dict: nil)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.List do
  @moduledoc """
  Module that define the struct of a Dict by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Dict

  ## Element added
    - `default` => Dict => default value

  ## Example
      iex> params = [id: :dict_test, default: %{default_key: default_value}]
      ...> Arke.Core.Parameter.Dict(%{type: :dict, opts: params})

  ## Return
      %Arke.Core.Parameter.Dict{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:default_list, :list, default_list: nil)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Date do
  @moduledoc """
  Module that define the struct of a Date by extending the Arke.Core.Parameter.base_parameters().

  ## Accepted values
  Date accepts the following format as values:
    - string => "YYYY-MM-DD" (separator must be - hyphen)
    - sigil => ~D[YYYY-MM-DD] (separator must be - hyphen)
    - struct => %Date{}

      {arke_struct} = Parameter.Date

  ## Element added
    - `default` => [values](#module-accepted-values) => default value

    ## Example
        iex> params = [id: :date_test, default: "1999-09-03"]
        ...> Arke.Core.Parameter.new(%{type: :date, opts: params})

    ## Return
        %Arke.Core.Parameter.Date{}
  """

  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:default_date, :date, default_date: nil)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Time do
  @moduledoc """
  Module that define the struct of a Time by extending the Arke.Core.Parameter.base_parameters().

  ## Accepted values
  Date accepts the following format as values:
    - string => "HH:MM:SS" (separator must be - hyphen)
    - sigil => ~T[HH:MM:SS] (separator must be - hyphen)
    - struct => %Time{}

      {arke_struct} = Parameter.Date

  ## Element added
    - `default` => [values](#module-accepted-values) => default value

    ## Example
        iex> params = [id: :time_test, default: "23:14:15"]
        ...> Arke.Core.Parameter.new(%{type: :time, opts: params})

    ## Return
        %Arke.Core.Parameter.Time{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:default_time, :time, default_time: nil)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.DateTime do
  @moduledoc """
  Module that define the struct of a DateTime by extending the Arke.Core.Parameter.base_parameters().

  ## Accepted values
  Date accepts the following format as values:
    - string => "YYYY-MM-DDTHH:MM:SSZ" | "YYYY-MM-DD HH:MM:SSZ" | "YYYY-MM-DD HH:MM:SS" (separator must be - hyphen for Date and colon :  for Time)
    - sigil => ~U[YYYY-MM-DDTHH:MM:SSZ] | ~U[YYYY-MM-DD HH:MM:SSZ] | ~N[YYYY-MM-DDTHH:MM:SSZ] | ~N[YYYY-MM-DD HH:MM:SSZ] |  ~N[YYYY-MM-DD HH:MM:SS] (separator must be - hyphen for Date and colon :  for Time)
    - struct => %DateTime{} | %NaiveDateTime{}

    The T separator is optional. If no offset is provided (Z will be added at the end)

      {arke_struct} = Parameter.Date

  ## Element added
    - `default` => [values](#module-accepted-values) => default value

    ## Example
        iex> params = [id: :time_test, default: "1999-12-12 09:08:07"]
        ...> Arke.Core.Parameter.new(%{type: :datetime, opts: params})

    ## Return
        %Arke.Core.Parameter.DateTime{}
  """

  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke id: :datetime do
    Parameter.base_parameters()
    parameter(:default_datetime, :datetime, default_datetime: nil)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Link do
  @moduledoc """
  Module that define the struct of a Link by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Link

  ## Element added
    - `link` => Link => link handler

  ## Example
      iex> params = [id: :dict_test, default: %{default_key: default_value}]
      ...> Arke.Core.Parameter.Dict(%{type: :dict, opts: params})

  ## Return
      %Arke.Core.Parameter.Dict{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:multiple, :boolean, default_boolean: false)

    parameter(:arke_or_group_id, :link,
      multiple: false,
      required: true,
      helper_text: "Arke or Group id"
    )

    parameter(:depth, :integer, default_integer: 0)
    parameter(:connection_type, :string, default_string: "link")
    parameter(:default_link, :link, default_link: nil)
    parameter(:filter_keys, :string, default_string: ["id", "arke_id"])
    parameter(:direction, :string)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Dynamic do
  @moduledoc """
  Module that define the struct of a Dynamic by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Dynamic

  ## Return
      %Arke.Core.Parameter.Dynamic{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:default_dynamic, :dynamic, default_dynamic: nil)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end

defmodule Arke.Core.Parameter.Binary do
  @moduledoc """
  Module that define the struct of a Binary by extending the Arke.Core.Parameter.base_parameters()
      {arke_struct} = Parameter.Binary

  ## Return
      %Arke.Core.Parameter.Binary{}
  """
  alias Arke.Core.Parameter
  alias Arke.Boundary.ParameterManager
  require Parameter
  use Arke.System

  arke do
    Parameter.base_parameters()
    parameter(:default_binary, :binary, default_binary: nil)
  end

  def on_create(_, %{metadata: %{project: project}} = unit) do
    ParameterManager.create(unit)
    {:ok, unit}
  end

  def on_update(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.update(id, project, unit)
    {:ok, unit}
  end

  def on_delete(_, %{id: id, metadata: %{project: project}} = unit) do
    ParameterManager.remove(unit)
    {:ok, unit}
  end
end