#include "Web_Event_Adapter.h"
#include "Gallery_Enum.h"

#include <algorithm>
#include <cmath>
#include <memory>
#include <string>

namespace renderive::web {
namespace {

std::optional<double> finite_number(const Json::Value& root, const char* name) {
    const auto& value = root[name];
    if (!value.isNumeric())
        return std::nullopt;
    const double result = value.asDouble();
    return std::isfinite(result) ? std::optional<double>(result) : std::nullopt;
}

std::optional<bool> boolean(const Json::Value& root, const char* name) {
    const auto& value = root[name];
    return value.isBool() ? std::optional<bool>(value.asBool()) : std::nullopt;
}

Keyboard_Modifier modifiers(const Json::Value& root) {
    const int value = root["modifiers"].isInt() ? root["modifiers"].asInt() : 0;
    return static_cast<Keyboard_Modifier>(std::clamp(value, 0, 15));
}

Mouse_Button mouse_button(const Json::Value& root) {
    const std::string value = root["button"].isString() ? root["button"].asString() : "none";
    return gallery_enum_cast<Mouse_Button>(value).value_or(Mouse_Button::None);
}

Key key(const Json::Value& root) {
    const std::string value = root["key"].isString() ? root["key"].asString() : "";
    if (const auto direct = magic_enum::enum_cast<Key>(value))
        return *direct;
    if (value == "ArrowLeft")
        return Key::Left;
    if (value == "ArrowRight")
        return Key::Right;
    if (value == "ArrowUp")
        return Key::Up;
    if (value == "ArrowDown")
        return Key::Down;
    return Key::Unknown;
}

std::optional<Render_2D_Web_Events> pointer_event(const Json::Value& root,
                                                   Event_Type type) {
    const auto x = finite_number(root, "x");
    const auto y = finite_number(root, "y");
    if (!x || !y)
        return std::nullopt;
    Pointer_Event event(type);
    event.position = {*x, *y};
    event.global_position = event.position;
    event.button = mouse_button(root);
    const int buttons = root["buttons"].isInt() ? root["buttons"].asInt() : 0;
    event.buttons = static_cast<Mouse_Button_Mask>(std::clamp(buttons, 0, 7));
    event.modifiers = modifiers(root);
    return event;
}

std::optional<Render_2D_Web_Events> wheel_event(const Json::Value& root) {
    const auto x = finite_number(root, "x");
    const auto y = finite_number(root, "y");
    const auto pixel_x = finite_number(root, "pixelDeltaX");
    const auto pixel_y = finite_number(root, "pixelDeltaY");
    const auto angle_x = finite_number(root, "angleDeltaX");
    const auto angle_y = finite_number(root, "angleDeltaY");
    if (!x || !y || !pixel_x || !pixel_y || !angle_x || !angle_y)
        return std::nullopt;
    Wheel_Event event;
    event.position = {*x, *y};
    event.global_position = event.position;
    event.pixel_delta_x = *pixel_x;
    event.pixel_delta_y = *pixel_y;
    event.angle_delta_x = *angle_x;
    event.angle_delta_y = *angle_y;
    event.modifiers = modifiers(root);
    const int buttons = root["buttons"].isInt() ? root["buttons"].asInt() : 0;
    event.buttons = static_cast<Mouse_Button_Mask>(std::clamp(buttons, 0, 7));
    return event;
}

std::optional<Render_2D_Web_Events> key_event(const Json::Value& root, Event_Type type) {
    Key_Event event(type);
    event.key = key(root);
    event.native_key = root["nativeKey"].isUInt() ? root["nativeKey"].asUInt() : 0;
    event.modifiers = modifiers(root);
    event.auto_repeat = root["repeat"].isBool() && root["repeat"].asBool();
    return event;
}

} // namespace

std::optional<Json::Value> parse_web_event(std::string_view message) {
    Json::CharReaderBuilder builder;
    builder["collectComments"] = false;
    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
    Json::Value root;
    std::string errors;
    if (!reader->parse(message.data(), message.data() + message.size(), &root, &errors) ||
        !root.isObject() || root["category"].asString() != "event" ||
        !root["type"].isString()) {
        return std::nullopt;
    }
    return root;
}

std::optional<Web_Transport_Events> Web_Transport_Event_Decoder::decode(
    const Json::Value& root, std::string_view source) {
    const std::string type = root["type"].asString();
    if (type == "frame")
        return Frame_Request{};
    if (type == "resize") {
        const auto width = finite_number(root, "width");
        const auto height = finite_number(root, "height");
        if (!width || !height)
            return std::nullopt;
        return Viewport_Resize{{static_cast<int>(std::clamp(*width, 240.0, 1920.0)),
                                static_cast<int>(std::clamp(*height, 180.0, 1200.0))}};
    }
    constexpr std::string_view gallery_prefix = "gallery_";
    if (!std::string_view(type).starts_with(gallery_prefix))
        return std::nullopt;
    const auto kind = gallery_enum_cast<Gallery_Request_Kind>(
        std::string_view(type).substr(gallery_prefix.size()));
    return kind ? std::optional<Web_Transport_Events>(
                      Gallery_Request{*kind, std::string(source)})
                : std::nullopt;
}

std::optional<Render_2D_Web_Events> Render_2D_Web_Event_Decoder::decode(
    const Json::Value& root, std::string_view) {
    const std::string type = root["type"].asString();
    if (type == "show")
        return Event(Event_Type::Show);
    if (type == "hide")
        return Event(Event_Type::Hide);
    if (type == "leave")
        return Event(Event_Type::Leave);
    if (type == "pointer_move")
        return pointer_event(root, Event_Type::Pointer_Move);
    if (type == "pointer_press")
        return pointer_event(root, Event_Type::Pointer_Press);
    if (type == "pointer_release")
        return pointer_event(root, Event_Type::Pointer_Release);
    if (type == "wheel")
        return wheel_event(root);
    if (type == "key_press")
        return key_event(root, Event_Type::Key_Press);
    if (type == "key_release")
        return key_event(root, Event_Type::Key_Release);
    return std::nullopt;
}

std::optional<Web_Demo_Control_Events> Web_Demo_Control_Decoder::decode(
    const Json::Value& root, std::string_view) {
    if (root["type"].asString() != "control" || !root["control"].isString())
        return std::nullopt;
    const std::string control = root["control"].asString();
    if (control == "mode" && root["value"].isString()) {
        const std::string value = root["value"].asString();
        const auto mode = magic_enum::enum_cast<Demo_Mode>(
            value, magic_enum::case_insensitive);
        return mode ? std::optional<Web_Demo_Control_Events>(Set_Demo_Mode{*mode})
                    : std::nullopt;
    }
    if (control == "center_frequency_mhz") {
        const auto value = finite_number(root, "value");
        return value && *value >= 1.0 && *value <= 6000.0
                   ? std::optional<Web_Demo_Control_Events>(Set_Center_Frequency{*value})
                   : std::nullopt;
    }
    if (control == "bandwidth_khz") {
        const auto value = finite_number(root, "value");
        return value && *value >= 10.0 && *value <= 50000.0
                   ? std::optional<Web_Demo_Control_Events>(Set_Bandwidth{*value})
                   : std::nullopt;
    }
    if (control == "gain_db") {
        const auto value = finite_number(root, "value");
        return value && *value >= -30.0 && *value <= 80.0
                   ? std::optional<Web_Demo_Control_Events>(Set_Gain{*value})
                   : std::nullopt;
    }
    if (control == "max_hold") {
        const auto value = boolean(root, "value");
        return value ? std::optional<Web_Demo_Control_Events>(Set_Max_Hold{*value})
                     : std::nullopt;
    }
    if (control == "smoothing") {
        const auto value = boolean(root, "value");
        return value ? std::optional<Web_Demo_Control_Events>(Set_Smoothing{*value})
                     : std::nullopt;
    }
    if (control == "clear_selection")
        return Clear_Selection{};
    return std::nullopt;
}

} // namespace renderive::web
