#pragma once

#include "assert.hpp"
#include "step_function.hpp"
#include "types.hpp"
#include <chrono>
#include <functional>
#include <type_traits>
#include "time.hpp"

enum class ReturnTag {
    Repeat,
    Next,
    Previous,
    Goto,
};

struct Return {
    ReturnTag tag;
    union {
        isize index;
    };
};

using namespace std::chrono_literals;

class Stepped {

using duration_type = std::chrono::system_clock::duration;
    using step_return_type = std::pair<Return, duration_type>;
    using step_type = std::function<step_return_type()>;

    std::vector<step_type> m_steps;

    isize m_current_step;
    duration_type m_current_wait_duration;
    util::Delta m_delta;

    auto inc_step() -> void;
    auto dec_step() -> void;

    public:
    Stepped() : m_current_step(0), m_steps(), m_delta(), m_current_wait_duration(0ms) {}
    
    auto add_step(const step_type& step) -> void;
    auto run() -> void;
    auto reset() -> void;


};

/// IMPLEMENTATION

using namespace std::chrono_literals;

auto Stepped::inc_step() -> void {
    if (++m_current_step >= m_steps.size()) {
        m_current_step = 0;
    }
}

auto Stepped::dec_step() -> void {
    if (--m_current_step < 0) {
        m_current_step = m_steps.size() - 1;
    }
}

auto Stepped::add_step(const step_type &step) -> void {
    m_steps.push_back(step);
}

auto Stepped::reset() -> void {
    m_current_step = 0;
    m_current_wait_duration = 0ms;
}

auto Stepped::run() -> void {
    if (m_delta.has_elapsed(m_current_wait_duration)) {
        util::assert(m_current_step < m_steps.size());

        auto&& [result, next_wait_duration] = m_steps[m_current_step]();

        switch (result.tag) {
        case ReturnTag::Repeat:
        break;
        case ReturnTag::Next:
        inc_step();
          break;
        case ReturnTag::Previous:
        dec_step();
          break;
        case ReturnTag::Goto: {
        util::assert(result.index < m_steps.size() && result.index >= 0);
       m_current_step = result.index;
          break;
        }
        }

        m_current_wait_duration = next_wait_duration;
    }
}