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

#include "board.h"
#include "rotor-light.hpp"
#include <string.h>

namespace rl = rotor_light;

namespace message {

struct ChangeInterval : rl::Message {
  static constexpr auto type_id = __LINE__;
  ChangeInterval(rl::ActorId to, const rl::Duration &delta_)
      : Message{to}, delta{delta_} {}
  rl::Duration delta;
};

struct Notify : rl::Message {
  static constexpr auto type_id = __LINE__;
  Notify(rl::ActorId to, const char *data_) : Message(to), data{data_} {}
  const char *data;
};

} // namespace message

struct Blinker : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    next_event = 0;
    subscribe(&Blinker::on_change_interval);
    Parent::initialize();
  }

  void advance_start() override {
    Parent::advance_start();
    next_event = add_event<rl::ctx::thread>(
        delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
  }

  void blink() {
    if (next_event) {
      supervisor->get_planner()->remove_event(next_event);
    }
    Board::toggle_led();
    send<rl::ctx::thread, message::Notify>(0, notifier_id, "blink\r\n");
    next_event = add_event<rl::ctx::thread>(
        delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
  }

  void on_change_interval(message::ChangeInterval &msg) {
    if (delay > msg.delta) {
      send<rl::ctx::thread, message::Notify>(0, notifier_id, "dec\r\n");
    } else {
      send<rl::ctx::thread, message::Notify>(0, notifier_id, "inc\r\n");
    }
    delay = msg.delta;
    blink();
  }

  rl::ActorId notifier_id;
  rl::Duration delay;
  rl::TimePoint next_event;
};

struct Notifier : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    ptr = end = nullptr;
    subscribe(&Notifier::on_notify);
    Parent::initialize();
  }

  void advance_start() override {
    Parent::advance_start();
    send<rl::ctx::thread, message::Notify>(0, id, "Ready\n");
  }

  void on_notify(message::Notify &message) {
    if (!ptr) { // prevent overloading
      ROTOR_LIGHT_DISABLE_INTERRUPTS();
      ptr = message.data;
      end = message.data + strlen(message.data);
      send_next();
      ROTOR_LIGHT_ENABLE_INTERRUPTS();
    }
  }

  void send_next() {
    if (end) {
      if (ptr == end) {
        ptr = nullptr;
        end = nullptr;
      } else {
        Board::send_uart(*ptr++);
      }
    }
  }

  volatile const char *ptr;
  const char *end;
};

static void app_hw_init();

using Supervisor =
    rl::Supervisor<rl::SupervisorBase::min_handlers_amount, Blinker, Notifier>;
using Storage =
    rl::traits::MessageStorage<rl::message::ChangeState,
                               rl::message::ChangeStateAck,
                               message::ChangeInterval, message::Notify>;
using Queue = rl::Queue<Storage, 5>; /* upto 5 messages in 1 queue */
using Planner = rl::Planner<2>;      /* upto 2 time events */

static void on_rx(char c);
static void on_tx();

Supervisor sup;

int main(int, char **) {
  Board::init_start();
  Board::enable_timer();
  Board::enable_uart(&on_rx, &on_tx);
  ROTOR_LIGHT_ENABLE_INTERRUPTS();

  /* setup */
  Queue queue;
  Planner planner;
  rl::Context context{&queue, &planner, &Board::get_now};
  sup.bind(context);

  auto blinker = sup.get_child<0>();
  blinker->delay = rl::Duration{1000000};
  blinker->notifier_id = sup.get_child<1>()->get_id();
  /* let it polls timer */
  sup.start(true);

  /* main cycle */
  sup.process();
  return 0;
}

static void on_rx(char c) {
  auto blinker = sup.get_child<0>();
  const auto &value = blinker->delay;
  if (c == '+') {
    auto new_value = value + value / 10;
    blinker->send<rl::ctx::thread, message::ChangeInterval>(
        0, blinker->get_id(), new_value);
  } else if (c == '-') {
    auto new_value = value - value / 10;
    blinker->send<rl::ctx::thread, message::ChangeInterval>(
        0, blinker->get_id(), new_value);
  }
}

static void on_tx() {
  auto notifier = sup.get_child<1>();
  notifier->send_next();
}
