<commit_before>defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmEvent do
  use GenServer
  require Logger

  alias FarmbotCore.{
    Asset,
    Asset.FarmEvent,
    Asset.Regimen,
    Asset.Sequence
  }

  alias FarmbotCeleryScript.{Scheduler, AST, Compiler}
  defstruct [:farm_event, :datetime, :handle_sequence, :handle_regimen]
  alias __MODULE__, as: State

  @checkup_time_ms Application.get_env(:farmbot_core, __MODULE__)[:checkup_time_ms]
  @checkup_time_ms ||
    Mix.raise("""
    config :farmbot_core, #{__MODULE__}, checkup_time_ms: 10_000
    """)

  def preload(%FarmEvent{}), do: []

  def tracks_changes?(%FarmEvent{}), do: false

  def start_link(farm_event, args) do
    GenServer.start_link(__MODULE__, [farm_event, args])
  end

  def init([farm_event, args]) do
    # Logger.disable(self())
    ensure_executable!(farm_event)
    now = DateTime.utc_now()
    handle_sequence = Keyword.get(args, :handle_sequence, &handle_sequence/2)
    handle_regimen = Keyword.get(args, :handle_regimen, &handle_regimen/3)

    unless is_function(handle_sequence, 2) do
      raise "FarmEvent Sequence handler should be a 2 arity function"
    end

    unless is_function(handle_regimen, 3) do
      raise "FarmEvent Regimen handler should be a 3 arity function"
    end

    state = %State{
      farm_event: farm_event,
      handle_regimen: handle_regimen,
      handle_sequence: handle_sequence,
      datetime: farm_event.last_executed || DateTime.utc_now()
    }

    # check if now is _before_ start_time
    case DateTime.compare(now, farm_event.start_time) do
      :lt ->
        init_event_started(state, now)

      _ ->
        # check if now is _after_ end_time
        case DateTime.compare(now, farm_event.end_time) do
          :gt -> init_event_completed(state, now)
          _ -> init_event_started(state, now)
        end
    end
  end

  defp init_event_completed(_, _) do
    Logger.warn("No future events")
    :ignore
  end

  def init_event_started(%State{} = state, _now) do
    {:ok, state, 0}
  end

  def handle_info(:timeout, %State{} = state) do
    Logger.info("build_calendar")
    next = FarmEvent.build_calendar(state.farm_event, state.datetime)

    if next do
      # positive if the first date/time comes after the second.
      diff = DateTime.compare(next, DateTime.utc_now())
      # if next_event is more than 0 milliseconds away, schedule that event.
      case diff do
        :gt ->
          Logger.info("Event is still in the future")
          {:noreply, state, @checkup_time_ms}

        diff when diff in [:lt, :eq] ->
          Logger.info("Event should be executed:  #{Timex.from_now(next)}")
          executable = ensure_executable!(state.farm_event)

          event =
            ensure_executed!(
              state.farm_event,
              executable,
              next,
              state.handle_sequence,
              state.handle_regimen
            )

          {:noreply, %{state | farm_event: event, datetime: DateTime.utc_now()}, @checkup_time_ms}
      end
    else
      Logger.warn("No more future events to execute.")
      {:stop, :normal, state}
    end
  end

  defp ensure_executed!(
         %FarmEvent{last_executed: nil} = event,
         %Sequence{} = exe,
         next_dt,
         handle_sequence,
         _
       ) do
    # positive if the first date/time comes after the second.
    comp = Timex.diff(DateTime.utc_now(), next_dt, :minutes)

    cond do
      # now is more than 2 minutes past expected execution time
      comp > 2 ->
        Logger.warn("Sequence: #{inspect(exe)} too late: #{comp} minutes difference.")
        event

      true ->
        Logger.warn("Sequence: #{inspect(exe)} has not run before: #{comp} minutes difference.")
        apply(handle_sequence, [exe, event.body])
        Asset.update_farm_event!(event, %{last_executed: next_dt})
    end
  end

  defp ensure_executed!(%FarmEvent{} = event, %Sequence{} = exe, next_dt, handle_sequence, _) do
    # positive if the first date/time comes after the second.
    comp = Timex.compare(event.last_executed, :minutes)

    cond do
      comp > 2 ->
        Logger.warn("Sequence: #{inspect(exe)} needs executing")
        apply(handle_sequence, [exe, event.body])
        Asset.update_farm_event!(event, %{last_executed: next_dt})

      0 ->
        Logger.warn("Sequence: #{inspect(exe)} already executed: #{Timex.from_now(next_dt)}")
        event
    end
  end

  defp ensure_executed!(
         %FarmEvent{last_executed: nil} = event,
         %Regimen{} = exe,
         next_dt,
         _,
         handle_regimen
       ) do
    Logger.warn("Regimen: #{inspect(exe)} has not run before. Executing it.")
    apply(handle_regimen, [exe, event, %{started_at: next_dt}])
    Asset.update_farm_event!(event, %{last_executed: next_dt})
  end

  defp ensure_executed!(%FarmEvent{} = event, %Regimen{} = exe, _next_dt, _, handle_regimen) do<commit_msg>Fix farm_events always needing execution<commit_after>defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FarmEvent do
  use GenServer
  require Logger

  alias FarmbotCore.{
    Asset,
    Asset.FarmEvent,
    Asset.Regimen,
    Asset.Sequence
  }

  alias FarmbotCeleryScript.{Scheduler, AST, Compiler}
  defstruct [:farm_event, :datetime, :handle_sequence, :handle_regimen]
  alias __MODULE__, as: State

  @checkup_time_ms Application.get_env(:farmbot_core, __MODULE__)[:checkup_time_ms]
  @checkup_time_ms ||
    Mix.raise("""
    config :farmbot_core, #{__MODULE__}, checkup_time_ms: 10_000
    """)

  def preload(%FarmEvent{}), do: []

  def tracks_changes?(%FarmEvent{}), do: false

  def start_link(farm_event, args) do
    GenServer.start_link(__MODULE__, [farm_event, args])
  end

  def init([farm_event, args]) do
    # Logger.disable(self())
    ensure_executable!(farm_event)
    now = DateTime.utc_now()
    handle_sequence = Keyword.get(args, :handle_sequence, &handle_sequence/2)
    handle_regimen = Keyword.get(args, :handle_regimen, &handle_regimen/3)

    unless is_function(handle_sequence, 2) do
      raise "FarmEvent Sequence handler should be a 2 arity function"
    end

    unless is_function(handle_regimen, 3) do
      raise "FarmEvent Regimen handler should be a 3 arity function"
    end

    state = %State{
      farm_event: farm_event,
      handle_regimen: handle_regimen,
      handle_sequence: handle_sequence,
      datetime: farm_event.last_executed || DateTime.utc_now()
    }

    # check if now is _before_ start_time
    case DateTime.compare(now, farm_event.start_time) do
      :lt ->
        init_event_started(state, now)

      _ ->
        # check if now is _after_ end_time
        case DateTime.compare(now, farm_event.end_time) do
          :gt -> init_event_completed(state, now)
          _ -> init_event_started(state, now)
        end
    end
  end

  defp init_event_completed(_, _) do
    Logger.warn("No future events")
    :ignore
  end

  def init_event_started(%State{} = state, _now) do
    {:ok, state, 0}
  end

  def handle_info(:timeout, %State{} = state) do
    Logger.info("build_calendar")
    next = FarmEvent.build_calendar(state.farm_event, state.datetime)

    if next do
      # positive if the first date/time comes after the second.
      diff = DateTime.compare(next, DateTime.utc_now())
      # if next_event is more than 0 milliseconds away, schedule that event.
      case diff do
        :gt ->
          Logger.info("Event is still in the future")
          {:noreply, state, @checkup_time_ms}

        diff when diff in [:lt, :eq] ->
          Logger.info("Event should be executed:  #{Timex.from_now(next)}")
          executable = ensure_executable!(state.farm_event)

          event =
            ensure_executed!(
              state.farm_event,
              executable,
              next,
              state.handle_sequence,
              state.handle_regimen
            )

          {:noreply, %{state | farm_event: event, datetime: DateTime.utc_now()}, @checkup_time_ms}
      end
    else
      Logger.warn("No more future events to execute.")
      {:stop, :normal, state}
    end
  end

  def handle_info({Scheduler, _, _}, state) do
    {:noreply, state, @checkup_time_ms}
  end

  defp ensure_executed!(
         %FarmEvent{last_executed: nil} = event,
         %Sequence{} = exe,
         next_dt,
         handle_sequence,
         _
       ) do
    # positive if the first date/time comes after the second.
    comp = Timex.diff(DateTime.utc_now(), next_dt, :minutes)

    cond do
      # now is more than 2 minutes past expected execution time
      comp > 2 ->
        Logger.warn("Sequence: #{inspect(exe)} too late: #{comp} minutes difference.")
        event

      true ->
        Logger.warn("Sequence: #{inspect(exe)} has not run before: #{comp} minutes difference.")
        apply(handle_sequence, [exe, event.body])
        Asset.update_farm_event!(event, %{last_executed: next_dt})
    end
  end

  defp ensure_executed!(%FarmEvent{} = event, %Sequence{} = exe, next_dt, handle_sequence, _) do
    # positive if the first date/time comes after the second.
    comp = Timex.diff(event.last_executed, next_dt, :seconds)
    cond do
      # last_execution is more than 2 minutes past expected execution time
      comp < -2000 ->
        Logger.warn("Sequence: #{exe.id} too late: #{comp} seconds difference.")
        Asset.update_farm_event!(event, %{last_executed: next_dt})
      # comp > 0 and comp < 2 ->
      #   Logger.warn("Sequence: #{exe.id} needs executing")
      #   apply(handle_sequence, [exe, event.body])
      #   Asset.update_farm_event!(event, %{last_executed: next_dt})
      comp < 0 ->
        Logger.warn("Sequence: #{exe.id} needs executing #{comp} seconds difference.")
        apply(handle_sequence, [exe, event.body])
        Asset.update_farm_event!(event, %{last_executed: next_dt})
      true ->
        Logger.warn("""
        what do?:
        last_executed=#{inspect(event.last_executed)}
        next_dt=#{inspect(next_dt)}
        comp=#{comp}
        """)
        event
    end
  end

  defp ensure_executed!(
         %FarmEvent{last_executed: nil} = event,
         %Regimen{} = exe,
         next_dt,
         _,
         handle_regimen
       ) do
    Logger.warn("Regimen: #{inspect(exe)} has not run before. Executing it.")
    apply(handle_regimen, [exe, event, %{started_at: next_dt}])
    Asset.update_farm_event!(event, %{last_executed: next_dt})
  end

  defp ensure_executed!(%FarmEvent{} = event, %Regimen{} = exe, _next_dt, _, handle_regimen) do
{'fraction_non_alphanumeric': 0.1101213040181956, 'fraction_numerical': 0.0037907505686125853, 'mean_word_length': 2.806998556998557, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6614072', 'n_tokens_mistral': 3495, 'n_tokens_neox': 3326, 'n_words': 827}