// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#pragma once

#include "definitions.hpp"
#include "rotor-light-bsp.h"
#include <cstddef>
#include <type_traits>

namespace rotor_light {

/** \struct TimeEvent
 *  \brief links together callback, data, timepoint and event id */
struct TimeEvent {
  TimeEvent() = default;
  TimeEvent(TimeEvent &&) = delete;
  TimeEvent(const TimeEvent &) = delete;

  /** copy-assignment operator */
  TimeEvent &operator=(const TimeEvent &other) = default;

  /** callback */
  Callback callback = nullptr;

  /** user-supplied data pointer */
  void *data;

  /** timepoint of the event expiration */
  TimePoint when = 0;

  /** unique event id */
  EventId event_id = 0;
};

struct PlannerBase;

/** \struct EventConsumer
 *  \brief iterator-like struct over expired time events */
struct EventConsumer {
  /** invokes expired event */
  void call_next();

  /** returns true if there are more exprired events */
  operator bool() const;

  ~EventConsumer();

private:
  friend struct PlannerBase;
  EventConsumer(TimePoint deadline, PlannerBase *watchdog);
  TimePoint deadline;
  PlannerBase *watchdog;
  int32_t index;
};

/** \struct PlannerBase
 *  \brief bookeeper of future events */
struct PlannerBase {
  /** records events storage pointer and maximum amount of events */
  PlannerBase(TimeEvent *events, int32_t events_count);
  PlannerBase(const PlannerBase &) = delete;
  PlannerBase(PlannerBase &&) = delete;

  /** \brief records the new event
   *
   *  the contextt is either ctx::thread (mask interrupts) or
   *  ctx::interrupt (do not mask interrupts).
   *
   *  returns cancellable event_id
   */
  template <typename Ctx>
  EventId add_event(TimePoint when, Callback callback, void *data) {
    if constexpr (std::is_same_v<Ctx, ctx::thread>) {
      ROTOR_LIGHT_DISABLE_INTERRUPTS();
    }
    EventId r;
    if (last_event + 1 >= events_count) {
      r = 0;
    } else {

      EventId future_id = next_id + 1;
      bool recheck = false;
      int i = last_event + 1;
      // find the insertion point (skip previous events)
      for (int j = 0; j <= last_event; ++j) {
        auto &event = events[j];
        if (event.event_id == future_id) {
          ++future_id;
          recheck = true;
        }
        if (event.when <= when) {
          continue;
        } else {
          i = j;
        }
      }
      while (recheck || !future_id) {
        recheck = false;
        if (!future_id) {
          ++future_id;
        }
        for (int j = 0; j <= last_event; ++j) {
          auto &event = events[j];
          if (event.event_id == future_id) {
            ++future_id;
            recheck = true;
            break;
          }
        }
      }
      // copy next events into the next position
      for (int j = last_event; j >= i; --j) {
        events[j + 1] = events[j];
      }
      events[i] = TimeEvent{callback, data, when, next_id};
      next_id = future_id;
      ++last_event;
      r = events[i].event_id;
    }

    if constexpr (std::is_same_v<Ctx, ctx::thread>) {
      ROTOR_LIGHT_ENABLE_INTERRUPTS();
    }
    return r;
  }

  /** cancels (removes) the specified event */
  void remove_event(EventId event_id);

  /** returns expired event iterator upto the deadline */
  EventConsumer consume(TimePoint deadline);

  /** returns timepoint of the next event */
  TimePoint next_event();

private:
  friend struct EventConsumer;
  void commit(size_t index);
  TimeEvent *events;
  int32_t events_count;
  int32_t last_event;
  EventId next_id;
};

/** \struct Planner
 *  \brief bookeeper of future events, templated for fixed amount of events */
template <size_t TimeEventsCount> struct Planner : PlannerBase {
  static_assert(TimeEventsCount > 0, "at least one event have to be allocated");
  Planner() : PlannerBase(events_holder, TimeEventsCount) {}

  /** storage for events */
  TimeEvent events_holder[TimeEventsCount];
};

} // namespace rotor_light
