// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#pragma once

#include <stdint.h>

#include <atomic>
#include <concepts>
#include <functional>
#include <string_view>
#include <thread>
#include <utility>

#include "wpi/hal/Types.h"
#include "wpi/units/frequency.hpp"
#include "wpi/units/time.hpp"
#include "wpi/util/mutex.hpp"

namespace wpi {

/**
 * Notifiers run a user-provided callback function on a separate thread.
 *
 * If StartSingle() is used, the callback will run once. If StartPeriodic() is
 * used, the callback will run repeatedly with the given period until stop() is
 * called.
 */
class Notifier {
 public:
  /**
   * Create a Notifier with the given callback.
   *
   * Configure when the callback runs with StartSingle() or StartPeriodic().
   *
   * @param callback The callback to run.
   */
  explicit Notifier(std::function<void()> callback)
      : Notifier(0, std::move(callback)) {}

  template <typename Arg, typename... Args>
  Notifier(std::invocable<Arg, Args...> auto&& callback, Arg&& arg,
           Args&&... args)
      : Notifier(std::bind(std::forward<decltype(callback)>(callback),
                           std::forward<Arg>(arg),
                           std::forward<Args>(args)...)) {}

  /**
   * Create a Notifier with the given callback.
   *
   * Configure when the callback runs with StartSingle() or StartPeriodic().
   *
   * This overload makes the underlying thread run with a real-time priority.
   * This is useful for reducing scheduling jitter on processes which are
   * sensitive to timing variance, like model-based control.
   *
   * @param priority The FIFO real-time scheduler priority ([1..99] where a
   *                 higher number represents higher priority). See "man 7
   *                 sched" for more details.
   * @param callback The callback to run.
   */
  explicit Notifier(int priority, std::function<void()> callback);

  template <typename Arg, typename... Args>
  Notifier(int priority, std::invocable<Arg, Args...> auto&& callback,
           Arg&& arg, Args&&... args)
      : Notifier(priority, std::bind(std::forward<decltype(callback)>(callback),
                                     std::forward<Arg>(arg),
                                     std::forward<Args>(args)...)) {}

  /**
   * Free the resources for a timer event.
   */
  ~Notifier();

  Notifier(Notifier&& rhs);
  Notifier& operator=(Notifier&& rhs);

  /**
   * Sets the name of the notifier. Used for debugging purposes only.
   *
   * @param name Name
   */
  void SetName(std::string_view name);

  /**
   * Change the callback function.
   *
   * @param callback The callback function.
   */
  void SetCallback(std::function<void()> callback);

  /**
   * Run the callback once after the given delay.
   *
   * @param delay Time to wait before the callback is called.
   */
  void StartSingle(wpi::units::second_t delay);

  /**
   * Run the callback periodically with the given period.
   *
   * The user-provided callback should be written so that it completes before
   * the next time it's scheduled to run.
   *
   * @param period Period after which to call the callback starting one
   *               period after the call to this method.
   */
  void StartPeriodic(wpi::units::second_t period);

  /**
   * Run the callback periodically with the given frequency.
   *
   * The user-provided callback should be written so that it completes before
   * the next time it's scheduled to run.
   *
   * @param frequency Frequency after which to call the callback starting one
   *                  period after the call to this method.
   */
  void StartPeriodic(wpi::units::hertz_t frequency);

  /**
   * Stop further callback invocations.
   *
   * No further periodic callbacks will occur. Single invocations will also be
   * cancelled if they haven't yet occurred.
   *
   * If a callback invocation is in progress, this function will block until the
   * callback is complete.
   */
  void Stop();

  /**
   * Gets the overrun count.
   *
   * An overrun occurs when a notifier's alarm is not serviced before the next
   * scheduled alarm time.
   *
   * @return overrun count
   */
  int32_t GetOverrun() const;

 private:
  // The thread waiting on the HAL alarm
  std::thread m_thread;

  // The mutex held while updating process information
  wpi::util::mutex m_processMutex;

  // HAL handle (atomic for proper destruction)
  std::atomic<HAL_NotifierHandle> m_notifier{0};

  // The user-provided callback
  std::function<void()> m_callback;
};

}  // namespace wpi
