// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <fmt/format.h>

#include "common/param_package.h"
#include "common/polyfill_ranges.h"
#include "common/polyfill_thread.h"
#include "common/settings.h"
#include "common/thread.h"
#include "input_common/drivers/joycon.h"
#include "input_common/helpers/joycon_driver.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"

namespace InputCommon {

Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
    // Avoid conflicting with SDL driver
    if (!Settings::values.enable_joycon_driver && !Settings::values.enable_procon_driver) {
        return;
    }
    LOG_INFO(Input, "Joycon driver Initialization started");
    const int init_res = SDL_hid_init();
    if (init_res == 0) {
        Setup();
    } else {
        LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
    }
}

Joycons::~Joycons() {
    Reset();
}

void Joycons::Reset() {
    scan_thread = {};
    for (const auto& device : left_joycons) {
        if (!device) {
            continue;
        }
        device->Stop();
    }
    for (const auto& device : right_joycons) {
        if (!device) {
            continue;
        }
        device->Stop();
    }
    for (const auto& device : pro_controller) {
        if (!device) {
            continue;
        }
        device->Stop();
    }
    SDL_hid_exit();
}

void Joycons::Setup() {
    u32 port = 0;
    PreSetController(GetIdentifier(0, Joycon::ControllerType::None));
    for (auto& device : left_joycons) {
        PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
        device = std::make_shared<Joycon::JoyconDriver>(port++);
    }
    port = 0;
    for (auto& device : right_joycons) {
        PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
        device = std::make_shared<Joycon::JoyconDriver>(port++);
    }
    port = 0;
    for (auto& device : pro_controller) {
        PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro));
        device = std::make_shared<Joycon::JoyconDriver>(port++);
    }

    scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
}

void Joycons::ScanThread(std::stop_token stop_token) {
    constexpr u16 nintendo_vendor_id = 0x057e;
    Common::SetCurrentThreadName("JoyconScanThread");

    do {
        SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
        SDL_hid_device_info* cur_dev = devs;

        while (cur_dev) {
            if (IsDeviceNew(cur_dev)) {
                LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
                          cur_dev->product_id);
                RegisterNewDevice(cur_dev);
            }
            cur_dev = cur_dev->next;
        }

        SDL_hid_free_enumeration(devs);
    } while (Common::StoppableTimedWait(stop_token, std::chrono::seconds{5}));
}

bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
    Joycon::ControllerType type{};
    Joycon::SerialNumber serial_number{};

    const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
    if (result != Common::Input::DriverResult::Success) {
        return false;
    }

    const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
    if (result2 != Common::Input::DriverResult::Success) {
        return false;
    }

    auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) {
        if (!device) {
            return false;
        }
        if (!device->IsConnected()) {
            return false;
        }
        if (device->GetHandleSerialNumber() != serial_number) {
            return false;
        }
        return true;
    };

    // Check if device already exist
    switch (type) {
    case Joycon::ControllerType::Left:
        if (!Settings::values.enable_joycon_driver) {
            return false;
        }
        for (const auto& device : left_joycons) {
            if (is_handle_identical(device)) {
                return false;
            }
        }
        break;
    case Joycon::ControllerType::Right:
        if (!Settings::values.enable_joycon_driver) {
            return false;
        }
        for (const auto& device : right_joycons) {
            if (is_handle_identical(device)) {
                return false;
            }
        }
        break;
    case Joycon::ControllerType::Pro:
        if (!Settings::values.enable_procon_driver) {
            return false;
        }
        for (const auto& device : pro_controller) {
            if (is_handle_identical(device)) {
                return false;
            }
        }
        break;
    default:
        return false;
    }

    return true;
}

void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
    Joycon::ControllerType type{};
    auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
    auto handle = GetNextFreeHandle(type);
    if (handle == nullptr) {
        LOG_WARNING(Input, "No free handles available");
        return;
    }
    if (result == Common::Input::DriverResult::Success) {
        result = handle->RequestDeviceAccess(device_info);
    }
    if (result == Common::Input::DriverResult::Success) {
        LOG_WARNING(Input, "Initialize device");

        const std::size_t port = handle->GetDevicePort();
        const Joycon::JoyconCallbacks callbacks{
            .on_battery_data = {[this, port, type](Joycon::Battery value) {
                OnBatteryUpdate(port, type, value);
            }},
            .on_color_data = {[this, port, type](Joycon::Color value) {
                OnColorUpdate(port, type, value);
            }},
            .on_button_data = {[this, port, type](int id, bool value) {
                OnButtonUpdate(port, type, id, value);
            }},
            .on_stick_data = {[this, port, type](int id, f32 value) {
                OnStickUpdate(port, type, id, value);
            }},
            .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
                OnMotionUpdate(port, type, id, value);
            }},
            .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
            .on_amiibo_data = {[this, port, type](const Joycon::TagInfo& tag_info) {
                OnAmiiboUpdate(port, type, tag_info);
            }},
            .on_camera_data = {[this, port](const std::vector<u8>& camera_data,
                                            Joycon::IrsResolution format) {
                OnCameraUpdate(port, camera_data, format);
            }},
        };

        handle->InitializeDevice();
        handle->SetCallbacks(callbacks);
    }
}

std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
    Joycon::ControllerType type) const {
    if (type == Joycon::ControllerType::Left) {
        const auto unconnected_device =
            std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
        if (unconnected_device != left_joycons.end()) {
            return *unconnected_device;
        }
    }
    if (type == Joycon::ControllerType::Right) {
        const auto unconnected_device = std::ranges::find_if(
            right_joycons, [](auto& device) { return !device->IsConnected(); });

        if (unconnected_device != right_joycons.end()) {
            return *unconnected_device;
        }
    }
    if (type == Joycon::ControllerType::Pro) {
        const auto unconnected_device = std::ranges::find_if(
            pro_controller, [](auto& device) { return !device->IsConnected(); });

        if (unconnected_device != pro_controller.end()) {
            return *unconnected_device;
        }
    }
    return nullptr;
}

bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
    const auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return false;
    }
    return handle->IsVibrationEnabled();
}

Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier,
                                                  const Common::Input::VibrationStatus& vibration) {
    const Joycon::VibrationValue native_vibration{
        .low_amplitude = vibration.low_amplitude,
        .low_frequency = vibration.low_frequency,
        .high_amplitude = vibration.high_amplitude,
        .high_frequency = vibration.high_frequency,
    };
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::DriverResult::InvalidHandle;
    }

    handle->SetVibration(native_vibration);
    return Common::Input::DriverResult::Success;
}

Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
                                             const Common::Input::LedStatus& led_status) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::DriverResult::InvalidHandle;
    }
    int led_config = led_status.led_1 ? 1 : 0;
    led_config += led_status.led_2 ? 2 : 0;
    led_config += led_status.led_3 ? 4 : 0;
    led_config += led_status.led_4 ? 8 : 0;

    return handle->SetLedConfig(static_cast<u8>(led_config));
}

Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
                                                     Common::Input::CameraFormat camera_format) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::DriverResult::InvalidHandle;
    }
    return handle->SetIrsConfig(Joycon::IrsMode::ImageTransfer,
                                static_cast<Joycon::IrsResolution>(camera_format));
};

Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
    return Common::Input::NfcState::Success;
};

Common::Input::NfcState Joycons::StartNfcPolling(const PadIdentifier& identifier) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::NfcState::Unknown;
    }
    return TranslateDriverResult(handle->StartNfcPolling());
};

Common::Input::NfcState Joycons::StopNfcPolling(const PadIdentifier& identifier) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::NfcState::Unknown;
    }
    return TranslateDriverResult(handle->StopNfcPolling());
};

Common::Input::NfcState Joycons::ReadAmiiboData(const PadIdentifier& identifier,
                                                std::vector<u8>& out_data) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::NfcState::Unknown;
    }
    return TranslateDriverResult(handle->ReadAmiiboData(out_data));
}

Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier,
                                              const std::vector<u8>& data) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::NfcState::Unknown;
    }
    return TranslateDriverResult(handle->WriteNfcData(data));
};

Common::Input::NfcState Joycons::ReadMifareData(const PadIdentifier& identifier,
                                                const Common::Input::MifareRequest& request,
                                                Common::Input::MifareRequest& data) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::NfcState::Unknown;
    }

    const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command);
    std::vector<Joycon::MifareReadChunk> read_request{};
    for (const auto& request_data : request.data) {
        if (request_data.command == 0) {
            continue;
        }
        Joycon::MifareReadChunk chunk = {
            .command = command,
            .sector_key = {},
            .sector = request_data.sector,
        };
        memcpy(chunk.sector_key.data(), request_data.key.data(),
               sizeof(Joycon::MifareReadChunk::sector_key));
        read_request.emplace_back(chunk);
    }

    std::vector<Joycon::MifareReadData> read_data(read_request.size());
    const auto result = handle->ReadMifareData(read_request, read_data);
    if (result == Common::Input::DriverResult::Success) {
        for (std::size_t i = 0; i < read_request.size(); i++) {
            data.data[i] = {
                .command = static_cast<u8>(command),
                .sector = read_data[i].sector,
                .key = {},
                .data = read_data[i].data,
            };
        }
    }
    return TranslateDriverResult(result);
};

Common::Input::NfcState Joycons::WriteMifareData(const PadIdentifier& identifier,
                                                 const Common::Input::MifareRequest& request) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        return Common::Input::NfcState::Unknown;
    }

    const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command);
    std::vector<Joycon::MifareWriteChunk> write_request{};
    for (const auto& request_data : request.data) {
        if (request_data.command == 0) {
            continue;
        }
        Joycon::MifareWriteChunk chunk = {
            .command = command,
            .sector_key = {},
            .sector = request_data.sector,
            .data = {},
        };
        memcpy(chunk.sector_key.data(), request_data.key.data(),
               sizeof(Joycon::MifareReadChunk::sector_key));
        memcpy(chunk.data.data(), request_data.data.data(), sizeof(Joycon::MifareWriteChunk::data));
        write_request.emplace_back(chunk);
    }

    return TranslateDriverResult(handle->WriteMifareData(write_request));
};

Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
                                                    const Common::Input::PollingMode polling_mode) {
    auto handle = GetHandle(identifier);
    if (handle == nullptr) {
        LOG_ERROR(Input, "Invalid handle {}", identifier.port);
        return Common::Input::DriverResult::InvalidHandle;
    }

    switch (polling_mode) {
    case Common::Input::PollingMode::Active:
        return handle->SetActiveMode();
    case Common::Input::PollingMode::Passive:
        return handle->SetPassiveMode();
    case Common::Input::PollingMode::IR:
        return handle->SetIrMode();
    case Common::Input::PollingMode::NFC:
        return handle->SetNfcMode();
    case Common::Input::PollingMode::Ring:
        return handle->SetRingConMode();
    default:
        return Common::Input::DriverResult::NotSupported;
    }
}

void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
                              Joycon::Battery value) {
    const auto identifier = GetIdentifier(port, type);
    if (value.charging != 0) {
        SetBattery(identifier, Common::Input::BatteryLevel::Charging);
        return;
    }

    Common::Input::BatteryLevel battery{};
    switch (value.status) {
    case 0:
        battery = Common::Input::BatteryLevel::Empty;
        break;
    case 1:
        battery = Common::Input::BatteryLevel::Critical;
        break;
    case 2:
        battery = Common::Input::BatteryLevel::Low;
        break;
    case 3:
        battery = Common::Input::BatteryLevel::Medium;
        break;
    case 4:
    default:
        battery = Common::Input::BatteryLevel::Full;
        break;
    }
    SetBattery(identifier, battery);
}

void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
                            const Joycon::Color& value) {
    const auto identifier = GetIdentifier(port, type);
    Common::Input::BodyColorStatus color{
        .body = value.body,
        .buttons = value.buttons,
        .left_grip = value.left_grip,
        .right_grip = value.right_grip,
    };
    SetColor(identifier, color);
}

void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
    const auto identifier = GetIdentifier(port, type);
    SetButton(identifier, id, value);
}

void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
    const auto identifier = GetIdentifier(port, type);
    SetAxis(identifier, id, value);
}

void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
                             const Joycon::MotionData& value) {
    const auto identifier = GetIdentifier(port, type);
    BasicMotion motion_data{
        .gyro_x = value.gyro_x,
        .gyro_y = value.gyro_y,
        .gyro_z = value.gyro_z,
        .accel_x = value.accel_x,
        .accel_y = value.accel_y,
        .accel_z = value.accel_z,
        .delta_timestamp = 15000,
    };
    SetMotion(identifier, id, motion_data);
}

void Joycons::OnRingConUpdate(f32 ring_data) {
    // To simplify ring detection it will always be mapped to an empty identifier for all
    // controllers
    static constexpr PadIdentifier identifier = {
        .guid = Common::UUID{},
        .port = 0,
        .pad = 0,
    };
    SetAxis(identifier, 100, ring_data);
}

void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type,
                             const Joycon::TagInfo& tag_info) {
    const auto identifier = GetIdentifier(port, type);
    const auto nfc_state = tag_info.uuid_length == 0 ? Common::Input::NfcState::AmiiboRemoved
                                                     : Common::Input::NfcState::NewAmiibo;

    const Common::Input::NfcStatus nfc_status{
        .state = nfc_state,
        .uuid_length = tag_info.uuid_length,
        .protocol = tag_info.protocol,
        .tag_type = tag_info.tag_type,
        .uuid = tag_info.uuid,
    };

    SetNfc(identifier, nfc_status);
}

void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
                             Joycon::IrsResolution format) {
    const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
    SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data});
}

std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
    auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
        if (!device) {
            return false;
        }
        if (!device->IsConnected()) {
            return false;
        }
        if (device->GetDevicePort() == identifier.port) {
            return true;
        }
        return false;
    };
    const auto type = static_cast<Joycon::ControllerType>(identifier.pad);

    if (type == Joycon::ControllerType::Left) {
        const auto matching_device = std::ranges::find_if(
            left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });

        if (matching_device != left_joycons.end()) {
            return *matching_device;
        }
    }

    if (type == Joycon::ControllerType::Right) {
        const auto matching_device = std::ranges::find_if(
            right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });

        if (matching_device != right_joycons.end()) {
            return *matching_device;
        }
    }

    if (type == Joycon::ControllerType::Pro) {
        const auto matching_device = std::ranges::find_if(
            pro_controller, [is_handle_active](auto& device) { return is_handle_active(device); });

        if (matching_device != pro_controller.end()) {
            return *matching_device;
        }
    }

    return nullptr;
}

PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
    const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0,
                                  0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)};
    return {
        .guid = Common::UUID{guid},
        .port = port,
        .pad = static_cast<std::size_t>(type),
    };
}

Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const {
    const auto identifier = GetIdentifier(port, type);
    return {
        {"engine", GetEngineName()},
        {"guid", identifier.guid.RawString()},
        {"port", std::to_string(identifier.port)},
        {"pad", std::to_string(identifier.pad)},
    };
}

std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
    std::vector<Common::ParamPackage> devices{};

    auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
        if (!device) {
            return;
        }
        if (!device->IsConnected()) {
            return;
        }
        auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType());
        std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
                                       device->GetDevicePort() + 1);
        param.Set("display", std::move(name));
        devices.emplace_back(param);
    };

    for (const auto& controller : left_joycons) {
        add_entry(controller);
    }
    for (const auto& controller : right_joycons) {
        add_entry(controller);
    }
    for (const auto& controller : pro_controller) {
        add_entry(controller);
    }

    // List dual joycon pairs
    for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
        if (!left_joycons[i] || !right_joycons[i]) {
            continue;
        }
        if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
            continue;
        }
        auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType());
        const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType());
        const auto type = Joycon::ControllerType::Dual;
        std::string name = fmt::format("{} {}", JoyconName(type), i + 1);

        main_param.Set("display", std::move(name));
        main_param.Set("guid2", second_param.Get("guid", ""));
        main_param.Set("pad", std::to_string(static_cast<size_t>(type)));
        devices.emplace_back(main_param);
    }

    return devices;
}

ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
    static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>,
                                18>
        switch_to_joycon_button = {
            std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true},
            {Settings::NativeButton::B, Joycon::PadButton::B, true},
            {Settings::NativeButton::X, Joycon::PadButton::X, true},
            {Settings::NativeButton::Y, Joycon::PadButton::Y, true},
            {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false},
            {Settings::NativeButton::DUp, Joycon::PadButton::Up, false},
            {Settings::NativeButton::DRight, Joycon::PadButton::Right, false},
            {Settings::NativeButton::DDown, Joycon::PadButton::Down, false},
            {Settings::NativeButton::L, Joycon::PadButton::L, false},
            {Settings::NativeButton::R, Joycon::PadButton::R, true},
            {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false},
            {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true},
            {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true},
            {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false},
            {Settings::NativeButton::Home, Joycon::PadButton::Home, true},
            {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false},
            {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false},
            {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true},
        };

    if (!params.Has("port")) {
        return {};
    }

    ButtonMapping mapping{};
    for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
        auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
        if (pad == Joycon::ControllerType::Dual) {
            pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left;
        }

        Common::ParamPackage button_params = GetParamPackage(port, pad);
        button_params.Set("button", static_cast<int>(joycon_button));
        mapping.insert_or_assign(switch_button, std::move(button_params));
    }

    // Map SL and SR buttons for left joycons
    if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
        Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left);

        Common::ParamPackage sl_button_params = button_params;
        Common::ParamPackage sr_button_params = button_params;
        sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
        sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
        mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
        mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
    }

    // Map SL and SR buttons for right joycons
    if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
        Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right);

        Common::ParamPackage sl_button_params = button_params;
        Common::ParamPackage sr_button_params = button_params;
        sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
        sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
        mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
        mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
    }

    return mapping;
}

AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
    if (!params.Has("port")) {
        return {};
    }

    const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
    auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
    auto pad_right = pad_left;
    if (pad_left == Joycon::ControllerType::Dual) {
        pad_left = Joycon::ControllerType::Left;
        pad_right = Joycon::ControllerType::Right;
    }

    AnalogMapping mapping = {};
    Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left);
    left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
    left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
    mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
    Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right);
    right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
    right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
    mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
    return mapping;
}

MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
    if (!params.Has("port")) {
        return {};
    }

    const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
    auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
    auto pad_right = pad_left;
    if (pad_left == Joycon::ControllerType::Dual) {
        pad_left = Joycon::ControllerType::Left;
        pad_right = Joycon::ControllerType::Right;
    }

    MotionMapping mapping = {};
    Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left);
    left_motion_params.Set("motion", 0);
    mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
    Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right);
    right_Motion_params.Set("motion", 1);
    mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
    return mapping;
}

Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
    const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
    switch (button) {
    case Joycon::PadButton::Left:
        return Common::Input::ButtonNames::ButtonLeft;
    case Joycon::PadButton::Right:
        return Common::Input::ButtonNames::ButtonRight;
    case Joycon::PadButton::Down:
        return Common::Input::ButtonNames::ButtonDown;
    case Joycon::PadButton::Up:
        return Common::Input::ButtonNames::ButtonUp;
    case Joycon::PadButton::LeftSL:
    case Joycon::PadButton::RightSL:
        return Common::Input::ButtonNames::TriggerSL;
    case Joycon::PadButton::LeftSR:
    case Joycon::PadButton::RightSR:
        return Common::Input::ButtonNames::TriggerSR;
    case Joycon::PadButton::L:
        return Common::Input::ButtonNames::TriggerL;
    case Joycon::PadButton::R:
        return Common::Input::ButtonNames::TriggerR;
    case Joycon::PadButton::ZL:
        return Common::Input::ButtonNames::TriggerZL;
    case Joycon::PadButton::ZR:
        return Common::Input::ButtonNames::TriggerZR;
    case Joycon::PadButton::A:
        return Common::Input::ButtonNames::ButtonA;
    case Joycon::PadButton::B:
        return Common::Input::ButtonNames::ButtonB;
    case Joycon::PadButton::X:
        return Common::Input::ButtonNames::ButtonX;
    case Joycon::PadButton::Y:
        return Common::Input::ButtonNames::ButtonY;
    case Joycon::PadButton::Plus:
        return Common::Input::ButtonNames::ButtonPlus;
    case Joycon::PadButton::Minus:
        return Common::Input::ButtonNames::ButtonMinus;
    case Joycon::PadButton::Home:
        return Common::Input::ButtonNames::ButtonHome;
    case Joycon::PadButton::Capture:
        return Common::Input::ButtonNames::ButtonCapture;
    case Joycon::PadButton::StickL:
        return Common::Input::ButtonNames::ButtonStickL;
    case Joycon::PadButton::StickR:
        return Common::Input::ButtonNames::ButtonStickR;
    default:
        return Common::Input::ButtonNames::Undefined;
    }
}

Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
    if (params.Has("button")) {
        return GetUIButtonName(params);
    }
    if (params.Has("axis")) {
        return Common::Input::ButtonNames::Value;
    }
    if (params.Has("motion")) {
        return Common::Input::ButtonNames::Engine;
    }

    return Common::Input::ButtonNames::Invalid;
}

std::string Joycons::JoyconName(Joycon::ControllerType type) const {
    switch (type) {
    case Joycon::ControllerType::Left:
        return "Left Joycon";
    case Joycon::ControllerType::Right:
        return "Right Joycon";
    case Joycon::ControllerType::Pro:
        return "Pro Controller";
    case Joycon::ControllerType::Dual:
        return "Dual Joycon";
    default:
        return "Unknown Switch Controller";
    }
}

Common::Input::NfcState Joycons::TranslateDriverResult(Common::Input::DriverResult result) const {
    switch (result) {
    case Common::Input::DriverResult::Success:
        return Common::Input::NfcState::Success;
    case Common::Input::DriverResult::Disabled:
        return Common::Input::NfcState::WrongDeviceState;
    case Common::Input::DriverResult::NotSupported:
        return Common::Input::NfcState::NotSupported;
    default:
        return Common::Input::NfcState::Unknown;
    }
}

} // namespace InputCommon
