#include "Time_Axis.h"

#include "Axis_Format.h"

#include <algorithm>
#include <cmath>

namespace renderive::detail {
namespace {

Time_Axis_State time_state_from(const Time_Axis_Properties& properties) {
    Time_Axis_State state;
    static_cast<Time_Axis_Properties&>(state) = properties;
    return state;
}

} // namespace

Time_Axis_Control::Time_Axis_Control(const Time_Axis_Properties& properties)
    : Axis_State_Strategy(time_state_from(properties)) {}

std::size_t Time_Axis_Control::time_point_count() const {
    return read([](const Time_Axis_State& state) { return state.samples.size(); });
}

int Time_Axis_Control::append_time(Time_Of_Day time) {
    int tick{};
    update([&](Time_Axis_State& state) {
        tick = state.next_tick++;
        state.samples.emplace_back(tick, time);
        const auto limit = static_cast<std::size_t>(std::max(512, state.visible_count.get() * 4));
        while (state.samples.size() > limit)
            state.samples.pop_front();
    });
    return tick;
}

Time_Of_Day Time_Axis_Control::tick_to_time(int tick) const {
    return read([tick](const Time_Axis_State& state) {
        const auto iterator = std::find_if(state.samples.begin(), state.samples.end(),
                                           [tick](const auto& value) { return value.first == tick; });
        return iterator == state.samples.end() ? Time_Of_Day{} : iterator->second;
    });
}

Range Time_Axis_Control::coordinate_range(const Time_Axis_State& state) const noexcept {
    const int latest = std::max(1, state.next_tick - 1);
    const int earliest = std::max(0, latest - state.visible_count.get() + 1);
    return state.newest_at_start ? Range{static_cast<double>(latest), static_cast<double>(earliest)}
                                 : Range{static_cast<double>(earliest), static_cast<double>(latest)};
}

double Time_Axis_Control::calculate_tick_step(Range range, const Time_Axis_State& state) const {
    const double available = static_cast<double>(state.pixel_length);
    const double label_width = std::max(48.0, state.unit_text_font.size * 7.0);
    const double label_count =
        std::max(1.0, available / (label_width + state.tick_label_spacing_px.get()));
    return std::max(1.0, std::ceil(range.size() / label_count));
}

std::string Time_Axis_Control::format_tick_label(double tick, const Time_Axis_State& state) const {
    const int target = static_cast<int>(std::llround(tick));
    const auto iterator = std::find_if(state.samples.begin(), state.samples.end(),
                                       [target](const auto& value) { return value.first == target; });
    return iterator == state.samples.end()
               ? std::string{}
               : formatted_axis_time(iterator->second, state.format);
}

} // namespace renderive::detail
