#include "Web_Plot_Session.h"
#include "Pixel_Frame.h"
#include "render_2D/export.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <mutex>
#include <type_traits>
#include <utility>
#include <vector>
namespace renderive::web {
namespace {
Color blend(Color first, Color second, double amount) {
    const auto channel = [amount](std::uint8_t a, std::uint8_t b) {
        return static_cast<std::uint8_t>(std::clamp(
            std::lround(a + (b - a) * amount), 0L, 255L));
    };
    return {
        channel(first.r, second.r), channel(first.g, second.g),
        channel(first.b, second.b), channel(first.a, second.a)
    };
}
Color_Map radio_color_map() {
    constexpr Color stops[] = {
        {3, 7, 18, 255}, {16, 52, 105, 255}, {23, 163, 184, 255},
        {238, 210, 91, 255}, {239, 68, 68, 255}
    };
    std::vector<Pixel> colors;
    colors.reserve(256);
    for (int index = 0; index < 256; ++index) {
        const double position = index / 255.0 * (std::size(stops) - 1);
        const auto stop = static_cast<std::size_t>(std::floor(position));
        const auto next = std::min(stop + 1, std::size(stops) - 1);
        colors.push_back(premultiply(blend(stops[stop], stops[next], position - stop)));
    }
    return Color_Map(std::move(colors));
}
Time_Of_Day current_time_of_day() {
    constexpr std::int64_t day_ms = 24LL * 60LL * 60LL * 1000LL;
    const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
        std::chrono::system_clock::now().time_since_epoch())
    .count();
    return {now % day_ms};
}
double gaussian(double x, double center, double width) {
    const double normalized = (x - center) / width;
    return std::exp(-0.5 * normalized * normalized);
}
} // namespace
struct Web_Plot_Session::Impl {
    Scene2D plot;
    renderive_Owner<Frequency_Axis> spectrum_frequency_axis;
    renderive_Owner<Axis> spectrum_power_axis;
    renderive_Owner<Frequency_Axis> waterfall_frequency_axis;
    renderive_Owner<Time_Axis> waterfall_time_axis;
    renderive_Owner<Spectrum> spectrum;
    renderive_Owner<Waterfall> waterfall;
    renderive_Owner<Selection_Rectangle_Overlay> selection;
    Demo_Mode mode = Demo_Mode::Fm;
    double gain_db = 8.0;
    std::uint64_t frame_index{};
    std::mutex mutex;
    Impl() {
        plot.init();
        plot.set_background_color({3, 7, 18, 255});
        plot.set_max_render_fps(30.0);
        plot.set_viewport_size({960, 600});
        const auto root = plot.root_renderable();
        constexpr Color axis_color{93, 116, 151, 255};
        const Range initial_frequency{101'300'000.0, 103'700'000.0};
        {
            auto attach = plot.attach_builder();
            const auto make_group = [&](std::string name) {
                auto group = detail::make_renderable_group(true);
                group->set_object_name(std::move(name));
                attach.attach(group);
                attach.add_display_parent(group, root);
                attach.add_dependency_parent(group, root);
                return group;
            };
            const auto data = make_group("Web_Data");
            const auto axes = make_group("Web_Axes");
            const auto overlay = make_group("Web_Overlay");
            axes->set_cache_mode(Renderable_Cache_Mode::Local_Pixel);
            spectrum_frequency_axis =
        Frequency_Axis::Builder(axes, Orientation::Horizontal, &attach)
        .set_coord_range(initial_frequency)
        .set_label_precision(2)
        .set_tick_length(8)
        .set_sub_tick_length(4)
        .set_color(axis_color)
        .set_use_wheel(true)
        .set_use_drag(true)
        .build();
        spectrum_power_axis =
        Axis::Builder(axes, Orientation::Vertical, &attach)
        .set_coord_range({-20.0, -120.0})
        .set_label_precision(0)
        .set_tick_length(-8)
        .set_sub_tick_length(-4)
        .set_color(axis_color)
        .set_unit_text("dBm")
        .build();
        waterfall_frequency_axis =
        Frequency_Axis::Builder(axes, Orientation::Horizontal, &attach)
        .set_coord_range(initial_frequency)
        .set_label_precision(2)
        .set_tick_length(8)
        .set_sub_tick_length(4)
        .set_color(axis_color)
        .set_use_wheel(true)
        .set_use_drag(true)
        .build();
        waterfall_time_axis =
        Time_Axis::Builder(axes, Orientation::Vertical, &attach)
        .set_visible_time_point_count(72)
        .set_tick_label_spacing_px(34)
        .set_time_format("mm:ss")
        .set_tick_length(-8)
        .set_sub_tick_length(-4)
        .set_color(axis_color)
        .build();
        spectrum = Spectrum::Builder{&attach}
                   .set<&Spectrum::Properties::frequency_range>(initial_frequency)
                   .set<&Spectrum::Properties::frequency_point_size>(768)
                   .set<&Spectrum::Properties::center_frequency>(102'500'000.0)
                   .set<&Spectrum::Properties::sweep_frequency_range>(Range{102'200'000.0, 102'800'000.0})
                   .set<&Spectrum::Properties::max_marker_visible>(true)
                   .set<&Spectrum::Properties::sweep_region_visible>(true)
                   .set<&Spectrum::Properties::interpolation_mode>(Line_Interpolation_Mode::Cubic_Value)
                   .build(data, spectrum_frequency_axis, spectrum_power_axis);
        spectrum->set<&Spectrum::Properties::current_pen>(Pen{
            Color{57, 224, 177, 255}, 2.0, Line_Style::Solid,
            Line_Cap::Round, Line_Join::Round
        });
        spectrum->set<&Spectrum::Properties::current_brush>(Brush{Color{31, 174, 145, 32}, Brush_Style::Solid});
        spectrum->set<&Spectrum::Properties::max_pen>(Pen{Color{250, 204, 21, 210}, 1.0});
        spectrum->set<&Spectrum::Properties::middle_frequency_pen>(Pen{
            Color{56, 189, 248, 220}, 1.0,
            Line_Style::Dash
        });
        waterfall = Waterfall::Builder{&attach}
                    .set<&Waterfall::Properties::frequency_range>(initial_frequency)
                    .set<&Waterfall::Properties::power_range>(Range{-120.0, -20.0})
                    .set<&Waterfall::Properties::frequency_bin_count>(768)
                    .set<&Waterfall::Properties::interpolation_mode>(Image_Interpolation_Mode::Bilinear)
                    .set<&Waterfall::Properties::color_map>(radio_color_map())
                    .build(data, waterfall_frequency_axis, waterfall_time_axis);
        selection = Selection_Rectangle_Overlay::Builder{&attach}
                    .set<&Selection_Rectangle_Overlay::Properties::selection_brush>(Brush{Color{56, 189, 248, 36}, Brush_Style::Solid})
                    .set<&Selection_Rectangle_Overlay::Properties::selection_border_pen>(Pen{Color{125, 211, 252, 230}, 1.0, Line_Style::Dash})
                    .build(overlay, spectrum_frequency_axis, spectrum_power_axis);
        }
        apply_layout(plot.viewport_size());
        plot.activate_view();
        update_model();
        (void)plot.render_frame(true);
    }
    ~Impl() {
        plot.deactivate_view();
    }
    void apply_layout(Size viewport) {
        const int left = viewport.width < 620 ? 54 : 72;
        const int right = viewport.width < 620 ? 44 : 70;
        const int top = 16;
        const int bottom = viewport.height < 480 ? 24 : 32;
        const int gap = viewport.height < 480 ? 36 : 48;
        const int content_width = std::max(1, viewport.width - left - right);
        const int available_height = std::max(2, viewport.height - top - bottom - gap);
        const int spectrum_height = std::max(1, available_height * 43 / 100);
        const int waterfall_y = top + spectrum_height + gap;
        const int waterfall_height = std::max(1, viewport.height - waterfall_y - bottom);
        spectrum_frequency_axis->update([&](Axis_Properties& state) {
            state.x = left;
            state.y = top + spectrum_height;
            state.pixel_length = static_cast<std::size_t>(content_width);
        });
        spectrum_power_axis->update([&](Axis_Properties& state) {
            state.x = left;
            state.y = top;
            state.pixel_length = static_cast<std::size_t>(spectrum_height);
        });
        waterfall_frequency_axis->update([&](Axis_Properties& state) {
            state.x = left;
            state.y = waterfall_y + waterfall_height;
            state.pixel_length = static_cast<std::size_t>(content_width);
        });
        waterfall_time_axis->update([&](auto& state) {
            state.x = left;
            state.y = waterfall_y;
            state.pixel_length = static_cast<std::size_t>(waterfall_height);
        });
    }
    void update_model() {
        constexpr int sample_count = 768;
        const double time = static_cast<double>(frame_index) * 0.075;
        std::vector<double> samples(sample_count);
        for (int index = 0; index < sample_count; ++index) {
            const double x = static_cast<double>(index) / (sample_count - 1);
            const double noise = std::sin(index * 12.9898 + frame_index * 0.371) *
            std::sin(index * 0.137 + frame_index * 0.071);
            double signal{};
            switch (mode) {
            case Demo_Mode::Am:
                signal = 66.0 * gaussian(x, 0.5, 0.008) +
                39.0 * gaussian(x, 0.42, 0.018) +
                39.0 * gaussian(x, 0.58, 0.018);
                break;
            case Demo_Mode::Fm: {
                const double moving = 0.5 + std::sin(time) * 0.035;
                signal = 58.0 * gaussian(x, moving, 0.055) +
                25.0 * gaussian(x, moving - 0.11, 0.023) +
                25.0 * gaussian(x, moving + 0.11, 0.023);
                break;
            }
            case Demo_Mode::Usb:
                signal = 61.0 * gaussian(x, 0.57, 0.045) +
                28.0 * gaussian(x, 0.68, 0.025);
                break;
            case Demo_Mode::Lsb:
                signal = 61.0 * gaussian(x, 0.43, 0.045) +
                28.0 * gaussian(x, 0.32, 0.025);
                break;
            }
            samples[index] = std::clamp(-110.0 + noise * 4.5 + signal + gain_db,
                                        -120.0, -20.0);
        }
        spectrum->update_samples(samples);
        if ((frame_index & 1U) == 0U)
            waterfall->append_row(current_time_of_day(), samples);
        ++frame_index;
    }
    void set_center_frequency(double megahertz) {
        const double center = megahertz * 1'000'000.0;
        const double bandwidth = spectrum_frequency_axis->get<&Axis_Properties::coordinates>().size();
        const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
        spectrum_frequency_axis->set<&Axis_Properties::coordinates>(range);
        waterfall_frequency_axis->set<&Axis_Properties::coordinates>(range);
        spectrum->set<&Spectrum::Properties::frequency_range>(range);
        spectrum->set<&Spectrum::Properties::center_frequency>(center);
        spectrum->set<&Spectrum::Properties::sweep_frequency_range>(Range{
            center - bandwidth * 0.125,
            center + bandwidth * 0.125
        });
        waterfall->set<&Waterfall::Properties::frequency_range>(range);
    }
    void set_bandwidth(double kilohertz) {
        const double center = spectrum->get<&Spectrum::Properties::center_frequency>();
        const double bandwidth = kilohertz * 1000.0;
        const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
        spectrum_frequency_axis->set<&Axis_Properties::coordinates>(range);
        waterfall_frequency_axis->set<&Axis_Properties::coordinates>(range);
        spectrum->set<&Spectrum::Properties::frequency_range>(range);
        spectrum->set<&Spectrum::Properties::sweep_frequency_range>(Range{
            center - bandwidth * 0.125,
            center + bandwidth * 0.125
        });
        waterfall->set<&Waterfall::Properties::frequency_range>(range);
    }
    std::optional<std::string> render_pixels() {
        if (!plot.view_active())
            return std::nullopt;
        update_model();
        if (!plot.render_frame(true))
            return std::nullopt;
        std::string pixels;
        const Color background = plot.background_color();
        plot.with_frame([&pixels, background](Image_View image) {
            pixels = encode_pixel_frame(image, background);
        });
        return pixels.empty() ? std::nullopt : std::optional<std::string>(std::move(pixels));
    }
    std::optional<Web_Response> handle(const Web_Event& event) {
        std::lock_guard lock(mutex);
        return std::visit(
            [this](const auto& value) -> std::optional<Web_Response> {
                using T = std::decay_t<decltype(value)>;
                if constexpr (std::is_same_v<T, Frame_Request>) {
                    auto pixels = render_pixels();
                    if (pixels)
                        return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)};
                }
                else if constexpr (std::is_same_v<T, Viewport_Resize>) {
                    const Size previous = plot.viewport_size();
                    plot.set_viewport_size(value.size);
                    apply_layout(value.size);
                    Resize_Event resized;
                    resized.old_size = previous;
                    resized.new_size = value.size;
                    plot.dispatch_event(resized);
                }
                else if constexpr (std::is_same_v<T, Event>) {
                    if (value.type == Event_Type::Show)
                        plot.activate_view();
                    else if (value.type == Event_Type::Hide)
                        plot.deactivate_view();
                    plot.dispatch_event(value);
                }
                else if constexpr (Event_Object<T>) {
                    plot.dispatch_event(value);
                }
                else if constexpr (std::is_same_v<T, Set_Demo_Mode>) {
                    mode = value.mode;
                    plot.notify_model_dirty();
                }
                else if constexpr (std::is_same_v<T, Set_Center_Frequency>) {
                    set_center_frequency(value.megahertz);
                }
                else if constexpr (std::is_same_v<T, Set_Bandwidth>) {
                    set_bandwidth(value.kilohertz);
                }
                else if constexpr (std::is_same_v<T, Set_Gain>) {
                    gain_db = value.decibels;
                    plot.notify_model_dirty();
                }
                else if constexpr (std::is_same_v<T, Set_Max_Hold>) {
                    spectrum->set<&Spectrum::Properties::max_hold_visible>(value.enabled);
                }
                else if constexpr (std::is_same_v<T, Set_Smoothing>) {
                    spectrum->set<&Spectrum::Properties::interpolation_mode>(
                        value.enabled
                            ? Line_Interpolation_Mode::Cubic_Value
                            : Line_Interpolation_Mode::Nearest_Sample);
                    waterfall->set<&Waterfall::Properties::interpolation_mode>(
                        value.enabled
                            ? Image_Interpolation_Mode::Bilinear
                            : Image_Interpolation_Mode::Nearest);
                }
                else if constexpr (std::is_same_v<T, Clear_Selection>) {
                    selection->clear_selected_regions();
                }
                return std::nullopt;
            },
            event);
    }
};
Web_Plot_Session::Web_Plot_Session() : impl_(std::make_unique<Impl>()) {}
Web_Plot_Session::~Web_Plot_Session() = default;
std::optional<Web_Response> Web_Plot_Session::handle(const Web_Event& event) {
    return impl_->handle(event);
}
} // namespace renderive::web
