#pragma once
#include <algorithm>
#include <limits>
#include <stdexcept>
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::Painter_Lease(Low_Latency_Strategy& strategy)
    : strategy_(&strategy), request_time_ns_(strategy.now_ns()), lease_lock_(strategy.painter_mutex_) {
    const std::uint64_t lease_acquired_time_ns = strategy.now_ns();
    const std::uint64_t state_request_time_ns = strategy.now_ns();
    {
        std::lock_guard<Mutex> lock(strategy.state_mutex_);
        const std::uint64_t state_acquired_time_ns = strategy.now_ns();
        frame_ = strategy.paint_;
        frame_->statistics = {};
        frame_->statistics.sequence = ++strategy.next_sequence_;
        frame_->statistics.real_time_data_update_sequence = strategy.real_time_data_update_sequence_;
        frame_->statistics.timing.paint_request_time_ns = request_time_ns_;
        frame_->statistics.timing.paint_lease_acquired_time_ns = lease_acquired_time_ns;
        frame_->statistics.timing.paint_lease_wait_ns = lease_acquired_time_ns - request_time_ns_;
        frame_->statistics.timing.paint_state_wait_ns = state_acquired_time_ns - state_request_time_ns;
        frame_->statistics.counters = strategy.counters_;
    }
    frame_->statistics.timing.paint_begin_time_ns = strategy.now_ns();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::Painter_Lease(Painter_Lease&& other) noexcept
    : strategy_(std::exchange(other.strategy_, nullptr)), frame_(std::exchange(other.frame_, nullptr)), request_time_ns_(other.request_time_ns_), lease_lock_(std::move(other.lease_lock_)) {}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::~Painter_Lease() {
    if (!strategy_) {
        return;
    }
    frame_->statistics.timing.paint_end_time_ns = strategy_->now_ns();
    frame_->statistics.timing.paint_duration_ns = frame_->statistics.timing.paint_end_time_ns - frame_->statistics.timing.paint_begin_time_ns;
    Observation abandoned_observation;
    Observation published_observation;
    bool abandoned{};
    const std::uint64_t state_request_time_ns = strategy_->now_ns();
    {
        std::lock_guard<Mutex> lock(strategy_->state_mutex_);
        const std::uint64_t state_acquired_time_ns = strategy_->now_ns();
        frame_->statistics.timing.publish_state_wait_ns = state_acquired_time_ns - state_request_time_ns;
        if (strategy_->cache_ready_) {
            ++strategy_->counters_.abandoned_frame_count;
            strategy_->cache_->statistics.counters = strategy_->counters_;
            abandoned_observation = {Observation_Event::abandoned, strategy_->cache_->statistics, strategy_->state_, strategy_->last_real_time_data_update_};
            abandoned = true;
        }
        std::swap(strategy_->paint_, strategy_->cache_);
        strategy_->cache_ready_ = true;
        ++strategy_->counters_.published_frame_count;
        frame_->statistics.timing.publish_time_ns = strategy_->now_ns();
        frame_->statistics.counters = strategy_->counters_;
        published_observation = {Observation_Event::published, frame_->statistics, strategy_->state_, strategy_->last_real_time_data_update_};
    }
    lease_lock_.unlock();
    if (abandoned) {
        strategy_->observe(abandoned_observation);
    }
    strategy_->observe(published_observation);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::operator bool() const {
    return frame_ != nullptr;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::operator->() -> Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::operator->() const -> const Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::operator*() -> Frame& {
    return *frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::operator*() const -> const Frame& {
    return *frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::get() -> Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Painter_Lease::get() const -> const Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::Render_Lease(Low_Latency_Strategy& strategy)
    : strategy_(&strategy), request_time_ns_(strategy.now_ns()), lease_lock_(strategy.render_mutex_) {
    const std::uint64_t lease_acquired_time_ns = strategy.now_ns();
    const std::uint64_t state_request_time_ns = strategy.now_ns();
    {
        std::lock_guard<Mutex> lock(strategy.state_mutex_);
        const std::uint64_t state_acquired_time_ns = strategy.now_ns();
        if (strategy.cache_ready_) {
            std::swap(strategy.cache_, strategy.render_);
            strategy.cache_ready_ = false;
            strategy.render_ready_ = true;
            acquired_new_frame_ = true;
        } else {
            ++strategy.counters_.swap_failure_count;
            swap_failed_ = true;
        }
        frame_ = strategy.render_ready_ ? strategy.render_ : nullptr;
        if (frame_) {
            ++frame_->statistics.frame_render_count;
            frame_->statistics.timing.render_request_time_ns = request_time_ns_;
            frame_->statistics.timing.render_lease_acquired_time_ns = lease_acquired_time_ns;
            frame_->statistics.timing.render_lease_wait_ns = lease_acquired_time_ns - request_time_ns_;
            frame_->statistics.timing.render_state_wait_ns = state_acquired_time_ns - state_request_time_ns;
            frame_->statistics.counters = strategy.counters_;
        }
        if (swap_failed_) {
            swap_failed_observation_.event = Observation_Event::swap_failed;
            swap_failed_observation_.state = strategy.state_;
            swap_failed_observation_.real_time_data = strategy.last_real_time_data_update_;
            if (frame_) {
                swap_failed_observation_.statistics = frame_->statistics;
            } else {
                swap_failed_observation_.statistics.counters = strategy.counters_;
            }
        }
    }
    if (frame_) {
        frame_->statistics.timing.render_begin_time_ns = strategy.now_ns();
        frame_->statistics.timing.frame_age_at_render_ns = frame_->statistics.timing.render_begin_time_ns - frame_->statistics.timing.publish_time_ns;
        if (acquired_new_frame_) {
            frame_->statistics.timing.ready_wait_ns = frame_->statistics.timing.frame_age_at_render_ns;
        }
    }
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::Render_Lease(Render_Lease&& other) noexcept
    : strategy_(std::exchange(other.strategy_, nullptr)), frame_(std::exchange(other.frame_, nullptr)), request_time_ns_(other.request_time_ns_), acquired_new_frame_(other.acquired_new_frame_), swap_failed_(other.swap_failed_), swap_failed_observation_(other.swap_failed_observation_), lease_lock_(std::move(other.lease_lock_)) {}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::~Render_Lease() {
    if (!strategy_) {
        return;
    }
    Observation rendered_observation;
    Observation lifecycle_observation;
    bool rendered{};
    bool lifecycle_completed{};
    if (frame_) {
        frame_->statistics.timing.render_end_time_ns = strategy_->now_ns();
        frame_->statistics.timing.render_duration_ns = frame_->statistics.timing.render_end_time_ns - frame_->statistics.timing.render_begin_time_ns;
        frame_->statistics.timing.end_to_end_ns = frame_->statistics.timing.render_end_time_ns - frame_->statistics.timing.paint_request_time_ns;
        const std::uint64_t state_request_time_ns = strategy_->now_ns();
        {
            std::lock_guard<Mutex> lock(strategy_->state_mutex_);
            const std::uint64_t state_acquired_time_ns = strategy_->now_ns();
            frame_->statistics.timing.render_finish_state_wait_ns = state_acquired_time_ns - state_request_time_ns;
            ++strategy_->counters_.render_pass_count;
            frame_->statistics.counters = strategy_->counters_;
            if (acquired_new_frame_) {
                strategy_->update_state(*frame_);
                lifecycle_observation = {Observation_Event::lifecycle_completed, frame_->statistics, strategy_->state_, strategy_->last_real_time_data_update_};
                lifecycle_completed = true;
            }
            rendered_observation = {Observation_Event::rendered, frame_->statistics, strategy_->state_, strategy_->last_real_time_data_update_};
            rendered = true;
        }
    }
    lease_lock_.unlock();
    if (swap_failed_) {
        strategy_->observe(swap_failed_observation_);
    }
    if (rendered) {
        strategy_->observe(rendered_observation);
    }
    if (lifecycle_completed) {
        strategy_->observe(lifecycle_observation);
    }
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::operator bool() const {
    return frame_ != nullptr;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::operator->() -> Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::operator->() const -> const Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::operator*() -> Frame& {
    return *frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::operator*() const -> const Frame& {
    return *frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::get() -> Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Render_Lease::get() const -> const Frame* {
    return frame_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::Low_Latency_Strategy(Observer observer, Configuration configuration)
    : Frame_Control_Strategy_Base(checked_frequency_hz(configuration.frequency_hz), frequency_interval_ns(configuration.frequency_hz)), observer_(std::move(observer)), paint_(&frames_[0]), cache_(&frames_[1]), render_(&frames_[2]) {
    state_.frequency_hz = configuration.frequency_hz;
    state_.target_interval_ns = frequency_interval_ns(configuration.frequency_hz);
    update_refresh_control();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::acquire_painter() -> Painter_Lease {
    return Painter_Lease(*this);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::acquire_renderer() -> Render_Lease {
    return Render_Lease(*this);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::set_frequency_hz(double frequency_hz) {
    frequency_hz = checked_frequency_hz(frequency_hz);
    std::lock_guard<Mutex> lock(state_mutex_);
    state_.frequency_limit_enabled = true;
    state_.frequency_hz = frequency_hz;
    state_.target_interval_ns = frequency_interval_ns(frequency_hz);
    update_refresh_control();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::clear_frequency_limit() {
    std::lock_guard<Mutex> lock(state_mutex_);
    state_.frequency_limit_enabled = false;
    state_.frequency_hz = invalid_frequency_hz();
    state_.target_interval_ns = 0;
    update_refresh_control();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::set_consumer_feedback(Frame_Consumer_Feedback feedback) {
    std::lock_guard<Mutex> lock(state_mutex_);
    update_consumer_estimator(feedback.observed_interval_ns);
    update_refresh_control();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::clear_consumer_feedback() {
    std::lock_guard<Mutex> lock(state_mutex_);
    state_.consumer_feedback_enabled = false;
    state_.consumer_sample_interval_ns = 0;
    state_.consumer_smoothed_interval_ns = 0;
    state_.consumer_variation_ns = 0;
    state_.consumer_safety_interval_ns = 0;
    state_.consumer_interval_ns = 0;
    update_refresh_control();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::swap() {
    swap_frame_control_state();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
double Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::frequency_hz() const noexcept {
    return render_frame_control_state().frequency_hz;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
std::uint64_t Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::next_refresh_interval_ns() const noexcept {
    return render_frame_control_state().next_refresh_interval_ns;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::frame_control_state() const -> Frame_Control_Strategy_Base::State {
    return render_frame_control_state();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::state() const -> State {
    std::lock_guard<Mutex> lock(state_mutex_);
    return state_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
auto Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::counter_statistics() const -> Counter_Statistics {
    std::lock_guard<Mutex> lock(state_mutex_);
    return counters_;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::on_real_time_data_update(const Real_Time_Data_Observation& observation) noexcept {
    Observation strategy_observation;
    {
        std::lock_guard<Mutex> lock(state_mutex_);
        ++real_time_data_update_sequence_;
        last_real_time_data_update_ = observation.state;
        state_.real_time_data_update_sequence = real_time_data_update_sequence_;
        Statistics statistics;
        statistics.real_time_data_update_sequence = real_time_data_update_sequence_;
        statistics.counters = counters_;
        strategy_observation = {Observation_Event::real_time_data_updated, statistics, state_, last_real_time_data_update_};
    }
    observe(strategy_observation);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
bool Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::discard_pending_frame() {
    Observation observation;
    bool discarded{};
    {
        std::lock_guard<Mutex> lock(state_mutex_);
        discarded = discard_pending_frame_locked(observation);
    }
    if (discarded) {
        observe(observation);
    }
    return discarded;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
bool Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::discard_pending_frame_before(std::uint64_t real_time_data_update_sequence) {
    Observation observation;
    bool discarded{};
    {
        std::lock_guard<Mutex> lock(state_mutex_);
        if (cache_ready_ && cache_->statistics.real_time_data_update_sequence < real_time_data_update_sequence) {
            discarded = discard_pending_frame_locked(observation);
        }
    }
    if (discarded) {
        observe(observation);
    }
    return discarded;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
bool Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::discard_stale_latest_data_frame() noexcept {
    Observation observation;
    bool discarded{};
    {
        std::lock_guard<Mutex> lock(state_mutex_);
        if (last_real_time_data_update_.retention == Real_Time_Data_Retention::latest && cache_ready_ && cache_->statistics.real_time_data_update_sequence < real_time_data_update_sequence_) {
            discarded = discard_pending_frame_locked(observation);
        }
    }
    if (discarded) {
        observe(observation);
    }
    return discarded;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
double Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::checked_frequency_hz(double frequency_hz) {
    if (!std::isfinite(frequency_hz)) {
        throw std::invalid_argument("frequency_hz must be finite");
    }
    return frequency_hz;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
std::uint64_t Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::now_ns() const noexcept {
    return observer_.now_ns();
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_refresh_control() {
    state_.next_refresh_interval_ns = std::max({state_.target_interval_ns,
                                               state_.bottleneck_duration_ns,
                                               state_.consumer_interval_ns});
    if (state_.consumer_feedback_enabled && state_.consumer_interval_ns != 0 &&
        state_.consumer_interval_ns >= state_.target_interval_ns &&
        state_.consumer_interval_ns >= state_.bottleneck_duration_ns) {
        state_.limit_state = Limit_State::consumer_limited;
    } else if (state_.frequency_limit_enabled && state_.target_interval_ns != 0 &&
               state_.target_interval_ns >= state_.bottleneck_duration_ns) {
        state_.limit_state = Limit_State::frequency_limited;
    } else if (state_.bottleneck_duration_ns != 0) {
        state_.limit_state = state_.paint_duration_ns >= state_.render_duration_ns ?
                                 Limit_State::paint_limited : Limit_State::render_limited;
    } else {
        state_.limit_state = Limit_State::unlimited;
    }
    update_frame_control_state(state_.frequency_hz, state_.next_refresh_interval_ns);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_consumer_estimator(std::uint64_t sample_interval_ns) {
    const std::uint64_t previous_sample = state_.consumer_sample_interval_ns;
    state_.consumer_sample_interval_ns = sample_interval_ns;
    if (!state_.consumer_feedback_enabled) {
        state_.consumer_feedback_enabled = true;
        state_.consumer_smoothed_interval_ns = sample_interval_ns;
        state_.consumer_variation_ns = 0;
    } else {
        const std::uint64_t sample_delta = sample_interval_ns >= previous_sample ?
                                               sample_interval_ns - previous_sample : previous_sample - sample_interval_ns;
        if (sample_delta >= state_.consumer_variation_ns)
            state_.consumer_variation_ns += (sample_delta - state_.consumer_variation_ns) / 16;
        else
            state_.consumer_variation_ns -= (state_.consumer_variation_ns - sample_delta) / 16;
        if (sample_interval_ns >= state_.consumer_smoothed_interval_ns)
            state_.consumer_smoothed_interval_ns += (sample_interval_ns - state_.consumer_smoothed_interval_ns) / 2;
        else
            state_.consumer_smoothed_interval_ns -= (state_.consumer_smoothed_interval_ns - sample_interval_ns) / 8;
    }
    const auto maximum = std::numeric_limits<std::uint64_t>::max();
    const std::uint64_t variation_margin = state_.consumer_variation_ns > maximum / 4 ?
                                               maximum : state_.consumer_variation_ns * 4;
    const std::uint64_t safety_interval = state_.consumer_smoothed_interval_ns > maximum - variation_margin ?
                                              maximum : state_.consumer_smoothed_interval_ns + variation_margin;
    state_.consumer_interval_ns = std::max(state_.consumer_smoothed_interval_ns, sample_interval_ns);
    state_.consumer_safety_interval_ns = std::max(state_.consumer_interval_ns, safety_interval);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::update_state(const Frame& frame) {
    state_.paint_duration_ns = frame.statistics.timing.paint_duration_ns;
    state_.render_duration_ns = frame.statistics.timing.render_duration_ns;
    state_.bottleneck_duration_ns = std::max(state_.paint_duration_ns, state_.render_duration_ns);
    state_.end_to_end_ns = frame.statistics.timing.end_to_end_ns;
    update_refresh_control();
    ++state_.completed_lifecycle_count;
    state_.frame_sequence = frame.statistics.sequence;
    state_.completed_frame_real_time_data_update_sequence = frame.statistics.real_time_data_update_sequence;
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
void Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::observe(const Observation& observation) noexcept {
    observer_.observe(observation);
}
template <class Scene_Frame, Mutex_Type Mutex, class Observer>
bool Low_Latency_Strategy<Scene_Frame, Mutex, Observer>::discard_pending_frame_locked(Observation& observation) {
    if (!cache_ready_) {
        return false;
    }
    cache_ready_ = false;
    ++counters_.manually_discarded_frame_count;
    cache_->statistics.counters = counters_;
    observation = {Observation_Event::manually_discarded, cache_->statistics, state_, last_real_time_data_update_};
    return true;
}
