/*
 * Copyright (c) 2025, Sönke Holz <soenke.holz@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/TypedTransfer.h>
#include <Kernel/Devices/Input/HID/Device.h>
#include <Kernel/Devices/Input/HID/KeyboardDriver.h>
#include <Kernel/Devices/Input/HID/KeyboardKeymap.h>
#include <Kernel/Devices/Input/Management.h>

#include <LibHID/ReportParser.h>

namespace Kernel::HID {

KeyboardDriver::~KeyboardDriver()
{
    InputManagement::the().detach_standalone_input_device(*m_keyboard_device);
}

ErrorOr<NonnullRefPtr<KeyboardDriver>> KeyboardDriver::create(Device const& device, ::HID::ApplicationCollection const& application_collection)
{
    auto keyboard_device = TRY(::KeyboardDevice::try_to_initialize());
    auto handler = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) KeyboardDriver(device, application_collection, move(keyboard_device))));
    InputManagement::the().attach_standalone_input_device(*handler->m_keyboard_device);
    return handler;
}

KeyboardDriver::KeyboardDriver(Device const& device, ::HID::ApplicationCollection const& application_collection, NonnullRefPtr<::KeyboardDevice> keyboard_device)
    : ApplicationCollectionDriver(device, application_collection)
    , m_keyboard_device(move(keyboard_device))
{
}

void KeyboardDriver::handle_keyboard_or_keypad_change_event(u16 usage_id, bool value)
{
    if (usage_id == 0)
        return;

    m_entropy_source.add_random_event(usage_id);

    m_keyboard_device->update_modifier(Mod_Keypad, false);

    RawKeyEvent raw_key_event;
    raw_key_event.is_press_down = value == 1;
    raw_key_event.scancode = usage_id;

    auto usage = static_cast<Usage>(usage_id | (to_underlying(UsagePage::KeyboardOrKeypad) << 16));

    using enum Usage;
    switch (usage) {
    case KeyboardLeftControl:
    case KeyboardRightControl:
        m_keyboard_device->update_modifier(Mod_Ctrl, raw_key_event.is_press());
        break;

    case KeyboardLeftShift:
    case KeyboardRightShift:
        m_keyboard_device->update_modifier(Mod_Shift, raw_key_event.is_press());
        break;

    case KeyboardLeftAlt:
        m_keyboard_device->update_modifier(Mod_Alt, raw_key_event.is_press());
        break;

    case KeyboardRightAlt:
        m_keyboard_device->update_modifier(Mod_AltGr, raw_key_event.is_press());
        break;

    case KeyboardLeftGUI:
    case KeyboardRightGUI:
        m_keyboard_device->update_modifier(Mod_Super, raw_key_event.is_press());
        break;

    default:
        break;
    };

    if (usage >= Usage::KeypadNumlock && usage <= Usage::KeypadDot)
        m_keyboard_device->update_modifier(Mod_Keypad, true);

    bool use_shifted_key_map = (m_keyboard_device->modifiers() & Mod_Shift) != 0 && usage_id < shifted_keyboard_keypad_page_keymap.size();
    auto key_map = use_shifted_key_map ? Span<KeyCodeEntry const>(shifted_keyboard_keypad_page_keymap) : Span<KeyCodeEntry const>(unshifted_keyboard_keypad_page_keymap);

    if (usage_id >= key_map.size()) {
        dbgln_if(HID_DEBUG, "HID: Unknown Keyboard Application Collection Usage ID: {:#x}", usage_id);
        return;
    }

    raw_key_event.code_entry = key_map[usage_id];

    KeyEvent key_event {
        .key = raw_key_event.code_entry.key_code,
        .map_entry_index = raw_key_event.code_entry.map_entry_index,
        .scancode = raw_key_event.scancode,
        .flags = raw_key_event.is_press() ? static_cast<u8>(Is_Press) : static_cast<u8>(0),
    };

    if (m_keyboard_device->num_lock_on() && (m_keyboard_device->modifiers() & Mod_Shift) == 0) {
        if (usage >= Usage::Keypad1 && usage <= Usage::KeypadDot) {
            auto index = (usage_id - (to_underlying(Usage::Keypad1) & 0xffff));
            static constexpr auto numpad_key_map = to_array<KeyCodeEntry>({
                { Key_1, 0x02 },
                { Key_2, 0x03 },
                { Key_3, 0x04 },
                { Key_4, 0x05 },
                { Key_5, 0x06 },
                { Key_6, 0x07 },
                { Key_7, 0x08 },
                { Key_8, 0x09 },
                { Key_9, 0x0a },
                { Key_0, 0x0b },
                { Key_Period, 0x34 },
            });

            key_event.key = numpad_key_map[index].key_code;
            key_event.map_entry_index = numpad_key_map[index].map_entry_index;
        }
    }

    if (key_event.key == Key_Invalid) {
        dbgln_if(HID_DEBUG, "HID: Unknown Keyboard Application Collection Usage ID: {:#x}", usage_id);
        return;
    }

    m_keyboard_device->handle_input_event(key_event);
}

ErrorOr<void> KeyboardDriver::on_report(ReadonlyBytes report_data)
{
    decltype(m_key_state) new_key_state {};

    TRY(::HID::parse_input_report(m_device.report_descriptor(), m_application_collection, report_data, [&new_key_state](::HID::Field const& field, i64 value) -> ErrorOr<IterationDecision> {
        u32 usage = 0;
        if (field.is_array) {
            if (!field.usage_minimum.has_value())
                return Error::from_errno(ENOTSUP); // TODO: What are we supposed to do here?

            usage = value + field.usage_minimum.value();
            value = 1;
        } else {
            usage = field.usage.value();
        }

        if ((usage >> 16) != to_underlying(UsagePage::KeyboardOrKeypad)) {
            dbgln_if(HID_DEBUG, "HID: Unknown Keyboard Application Collection Usage: {:#x}", usage);
            return IterationDecision::Continue;
        }

        u16 usage_id = usage & 0xffff;

        if (usage_id >= key_state_bitmap_size_in_bits) {
            dbgln_if(HID_DEBUG, "HID: Unknown Keyboard/Keypad Page Usage ID: {:#x}", usage_id);
            return IterationDecision::Continue;
        }

        if (value == 1)
            new_key_state[usage_id / key_state_bitmap_bits_per_element] |= 1zu << (usage_id % key_state_bitmap_bits_per_element);

        return IterationDecision::Continue;
    }));

    // FIXME: Optimize this.
    for (size_t i = 0; i < key_state_bitmap_size_in_bits; i++) {
        auto old_bitmap_element = m_key_state[i / key_state_bitmap_bits_per_element];
        auto new_bitmap_element = new_key_state[i / key_state_bitmap_bits_per_element];

        bool old_value = old_bitmap_element & (1zu << (i % key_state_bitmap_bits_per_element));
        bool new_value = new_bitmap_element & (1zu << (i % key_state_bitmap_bits_per_element));

        if (old_value != new_value)
            handle_keyboard_or_keypad_change_event(i, new_value);
    }

    AK::TypedTransfer<KeyStateBitmapElement>::copy(m_key_state.data(), new_key_state.data(), m_key_state.size());

    return {};
}

}
