lib/arke.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 do
  alias Arke.Core.Unit
  alias Arke.Boundary.{ArkeManager, GroupManager, ParameterManager}
  alias Arke.System.BaseParameter

  def init(), do: :ok

  defp get_module(data,type) do
    # get all the arke modules which has the arke macro defined
    # find the right module for the given data and return it
    arke_module_list = Enum.reduce(:application.loaded_applications(), [], fn {app, _, _}, arke_list ->
      {:ok, modules} = :application.get_key(app, :modules)

      function_name = get_module_fn(type)

      module_arke_list =
        Enum.reduce(modules, [], fn mod, mod_arke_list ->
          if Code.ensure_loaded?(mod) and :erlang.function_exported(mod, function_name, 0) and
             apply(mod, function_name,[]) != nil do
            [%{module: mod, arke_id: apply(mod, function_name,[]).id} | mod_arke_list]
          else
            mod_arke_list
          end
        end)


      arke_list ++ module_arke_list
    end)
    Enum.find(arke_module_list,%{module: nil, arke_id: nil}, fn k ->
    if is_map(k) do
      to_string(k.arke_id) == to_string(Map.get(data,:id))
                 end
    end)[:module]
  end
  defp get_module_fn("arke"), do: :arke_from_attr
  defp get_module_fn("group"), do: :group_from_attr

  def handle_manager(_data,_project,_arke_id,_error\\[])
  def handle_manager([data | t],project,:parameter,error)do
    {type, updated_data} = Map.pop(data,:type)
    final_data = BaseParameter.check_enum(type, updated_data) |> Enum.into(%{})
    updated_error = start_manager(final_data,type,project,ParameterManager,nil)
    handle_manager(t,project, :parameter,updated_error ++ error)
  end

  def handle_manager([data | t],project,:arke,error) do
    {flatten_data,other} = Map.pop(data,:data,%{})
    updated_data = Map.merge(flatten_data,other)
                   |> Map.put(:type,Map.get(data,:type,"arke"))
                   |> Map.put_new(:active,true)

    final_data = Map.replace(updated_data,:parameters,parse_arke_parameter(updated_data,project))
    module = get_module(final_data,"arke")
    updated_error = start_manager(final_data,"arke",project,ArkeManager, module)
    handle_manager(t,project, :arke,updated_error ++ error)
  end

  def handle_manager([data | t],project,:group,error)do
    loaded_list = Enum.reduce(Map.get(data,:arke_list,[]),[], fn arke,acc ->
      with %{id: id, metadata: _metadata}=link_data <- parse_group_member(arke),
           %Unit{}= _arke_unit <- ArkeManager.get(id, project) do
          [link_data | acc]
        else
          _ ->
            [%{context: :arke_list_group , message: "invalid arke in arke_list of: `#{data.id}` in `#{project}`"} | error]
      end

    end)

    final_data=  Map.put(data,:arke_list,loaded_list)
    module = get_module(final_data,"group")
    updated_error = start_manager(final_data,"group",project,GroupManager,module)

    handle_manager(t,project, :group,updated_error ++ error)
  end

  def handle_manager([],_project,_arke_id,error),do: error

  defp parse_group_member(member) when is_binary(member), do: %{id: String.to_atom(member), metadata: %{}}
  defp parse_group_member(member) when is_map(member) do
    case Map.get(member,:id) do
    nil -> {:error,"arke id not found"}
    id -> %{id: id, metadata: Map.get(member, :metadata, %{})}
    end
 end

  defp start_manager(_data,_type,_project, _manager, _module,_error\\[])

  defp start_manager(data,type,project, manager, module,error) do
    case Map.pop(data,:id,nil) do
      {nil, _updated_data} -> [%{context: :manager , message: "key id not found"} | error]
      {id, updated_data} ->
        {metadata,updated_data} = Map.pop(updated_data,:metadata,%{project: project})
        case manager.create(
                                    Unit.new(
                                      String.to_atom(id),
                                      updated_data,
                                      String.to_atom(type),
                                      nil,
                                      metadata,
                                      nil,
                                      nil,
                                      module
                                    ),
                                    project
                                  ) do
                               %Unit{} = _unit ->
                                 error
                               _ ->[%{context: :manager , message: "cannot start manager for: `#{id}`"} | error]
                             end
    end

  end
  defp parse_arke_parameter(data,project) do
    base_parameters(Map.get(data,:parameters,[]), Map.get(data,:type,"arke")) |> Enum.reduce([], fn param,acc ->
      # controllare anche che il parameter manager torni qualcosa che esiste
      converted = Map.update(param,:id, "tbd", &String.to_atom(to_string(&1)))
      id = converted[:id]


      case ParameterManager.get(id,project) do
        %Unit{} = arke ->
        type = arke.arke_id
          final_data = Map.update(converted,:metadata,%{}, &(
            BaseParameter.check_enum(type, &1) |>
              Enum.into(%{}))
          )
          |> Map.put(:arke, type)
          [ final_data | acc]
        _ -> acc
      end
    end)
  end

  defp base_parameters(arke_parameters,"arke") do
    arke_parameters ++ [
      %{id: "id", metadata: %{required: true, persistence: "table_column"}},
      %{id: "arke_id", metadata: %{required: false, persistence: "table_column"}},
      %{id: "metadata", metadata: %{required: false, persistence: "table_column"}},
      %{id: "inserted_at", metadata: %{required: false, persistence: "table_column"}},
      %{id: "updated_at", metadata: %{required: false, persistence: "table_column"}}]
  end
  defp base_parameters(arke_parameters, _type), do: arke_parameters
end