#include "Performance_Overlay.h"

#include <algorithm>
#include <atomic>
#include <cctype>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <mutex>
#include <sstream>

namespace renderive {
namespace {

std::string safe_filename(std::string value) {
    if (value.empty())
        return "plot";
    for (char& character : value) {
        const auto byte = static_cast<unsigned char>(character);
        if (!(std::isalnum(byte) || character == '-' || character == '_'))
            character = '_';
    }
    return value;
}

std::string fixed(double value, int precision = 2) {
    std::ostringstream stream;
    stream << std::fixed << std::setprecision(precision) << value;
    return stream.str();
}

} // namespace

struct Performance_Overlay::Impl {
    mutable std::mutex mutex;
    std::atomic_bool enabled{};
    Performance_Overlay_Options options;
    std::string plot_name;
    std::shared_ptr<const Performance_Display_Snapshot> snapshot;
    std::ofstream log;
    std::filesystem::path log_path;
    std::chrono::steady_clock::time_point previous_frame;
};

Performance_Overlay::Performance_Overlay()
    : Performance_Overlay(Performance_Overlay_Options{}) {}

Performance_Overlay::Performance_Overlay(Performance_Overlay_Options options)
    : impl_(std::make_unique<Impl>()) {
    impl_->options = std::move(options);
}

Performance_Overlay::~Performance_Overlay() = default;

void Performance_Overlay::set_enabled(bool enabled) {
    if (impl_->enabled.exchange(enabled, std::memory_order_acq_rel) == enabled)
        return;
    std::lock_guard lock(impl_->mutex);
    impl_->previous_frame = {};
    if (!enabled) {
        impl_->log.close();
        impl_->log_path.clear();
    }
}

bool Performance_Overlay::enabled() const noexcept {
    return impl_->enabled.load(std::memory_order_acquire);
}

void Performance_Overlay::set_options(Performance_Overlay_Options options) {
    std::lock_guard lock(impl_->mutex);
    impl_->options = std::move(options);
    impl_->log.close();
    impl_->log_path.clear();
}

std::shared_ptr<const Performance_Display_Snapshot>
Performance_Overlay::display_snapshot() const {
    std::lock_guard lock(impl_->mutex);
    return impl_->snapshot;
}

void Performance_Overlay::set_plot_name(std::string name) {
    std::lock_guard lock(impl_->mutex);
    impl_->plot_name = std::move(name);
    impl_->log.close();
    impl_->log_path.clear();
}

void Performance_Overlay::record_frame(double render_duration_ms,
                                       const Low_Latency_Diagnostics& diagnostics,
                                       std::size_t renderable_count,
                                       Size viewport) {
    if (!enabled())
        return;

    {
        std::lock_guard lock(impl_->mutex);
        if (!enabled())
            return;
        const auto now = std::chrono::steady_clock::now();
        double rendered_fps{};
        if (impl_->previous_frame.time_since_epoch().count() != 0) {
            const auto seconds = std::chrono::duration<double>(now - impl_->previous_frame).count();
            if (seconds > 0.0)
                rendered_fps = 1.0 / seconds;
        }
        impl_->previous_frame = now;

        auto snapshot = std::make_shared<Performance_Display_Snapshot>();
        snapshot->style = impl_->options.style;
        snapshot->viewport_size = viewport;
        snapshot->plot_name = impl_->plot_name;
        snapshot->frame_count = diagnostics.refresh.frame_count;
        snapshot->lines = {
            "Renderive Core2 / Kernel",
            "Plot: " + (impl_->plot_name.empty() ? std::string{"unnamed"} : impl_->plot_name),
            "Frame: " + std::to_string(snapshot->frame_count),
            "Render: " + fixed(render_duration_ms, 3) + " ms",
            "Observed: " + fixed(rendered_fps) + " FPS",
            "Limit: " + fixed(diagnostics.refresh.frequency_hz) + " FPS",
            "Kernel interval: " + fixed(diagnostics.refresh.next_refresh_interval_ns / 1'000'000.0, 3) + " ms",
            "Renderables: " + std::to_string(renderable_count)
        };

        impl_->snapshot = std::move(snapshot);
        const auto& log_options = impl_->options.log;
        if (log_options.enabled && !log_options.directory.empty()) {
            std::error_code error;
            const std::filesystem::path directory(log_options.directory);
            std::filesystem::create_directories(directory, error);
            const auto path = directory /
                (safe_filename(log_options.session_name) + "_" + safe_filename(impl_->plot_name) + ".csv");
            if (!error && (!impl_->log.is_open() || impl_->log_path != path)) {
                impl_->log.close();
                const bool exists = std::filesystem::exists(path, error);
                impl_->log.open(path, std::ios::out | std::ios::app);
                impl_->log_path = path;
                if (impl_->log && !exists)
                    impl_->log << "frame,render_ms,observed_fps,max_fps,renderables\n";
            }
            if (impl_->log) {
                impl_->log << diagnostics.refresh.frame_count << ',' << render_duration_ms << ','
                           << rendered_fps << ',' << diagnostics.refresh.frequency_hz << ','
                           << renderable_count << '\n';
                impl_->log.flush();
            }
        }
    }
}

} // namespace renderive
