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

#include <common/fs/fs.h>
#include <common/fs/path_util.h>
#include <common/settings.h>
#include <hid_core/hid_types.h>
#include <jni.h>

#include "android_config.h"
#include "common/android/android_common.h"
#include "common/android/id_cache.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "input_common/drivers/android.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/drivers/virtual_gamepad.h"
#include "native.h"

std::unordered_map<std::string, std::unique_ptr<AndroidConfig>> map_profiles;

bool IsHandheldOnly() {
    const auto npad_style_set =
        EmulationSession::GetInstance().System().HIDCore().GetSupportedStyleTag();

    if (npad_style_set.fullkey == 1) {
        return false;
    }

    if (npad_style_set.handheld == 0) {
        return false;
    }

    return !Settings::IsDockedMode();
}

std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
    return filename.replace_extension();
}

bool IsProfileNameValid(std::string_view profile_name) {
    return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos;
}

bool ProfileExistsInFilesystem(std::string_view profile_name) {
    return Common::FS::Exists(Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "input" /
                              fmt::format("{}.ini", profile_name));
}

bool ProfileExistsInMap(const std::string& profile_name) {
    return map_profiles.find(profile_name) != map_profiles.end();
}

bool SaveProfile(const std::string& profile_name, std::size_t player_index) {
    if (!ProfileExistsInMap(profile_name)) {
        return false;
    }

    Settings::values.players.GetValue()[player_index].profile_name = profile_name;
    map_profiles[profile_name]->SaveAndroidControlPlayerValues(player_index);
    return true;
}

bool LoadProfile(std::string& profile_name, std::size_t player_index) {
    if (!ProfileExistsInMap(profile_name)) {
        return false;
    }

    if (!ProfileExistsInFilesystem(profile_name)) {
        map_profiles.erase(profile_name);
        return false;
    }

    LOG_INFO(Config, "Loading input profile `{}`", profile_name);

    Settings::values.players.GetValue()[player_index].profile_name = profile_name;
    map_profiles[profile_name]->ReadAndroidControlPlayerValues(player_index);
    return true;
}

void ApplyControllerConfig(size_t player_index,
                           const std::function<void(Core::HID::EmulatedController*)>& apply) {
    auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
    if (player_index == 0) {
        auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
        auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
        handheld->EnableConfiguration();
        player_one->EnableConfiguration();
        apply(handheld);
        apply(player_one);
        handheld->DisableConfiguration();
        player_one->DisableConfiguration();
        handheld->SaveCurrentConfig();
        player_one->SaveCurrentConfig();
    } else {
        auto* controller = hid_core.GetEmulatedControllerByIndex(player_index);
        controller->EnableConfiguration();
        apply(controller);
        controller->DisableConfiguration();
        controller->SaveCurrentConfig();
    }
}

void ConnectController(size_t player_index, bool connected) {
    auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
    if (player_index == 0) {
        auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
        auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
        handheld->EnableConfiguration();
        player_one->EnableConfiguration();
        if (player_one->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) {
            if (connected) {
                handheld->Connect();
            } else {
                handheld->Disconnect();
            }
            player_one->Disconnect();
        } else {
            if (connected) {
                player_one->Connect();
            } else {
                player_one->Disconnect();
            }
            handheld->Disconnect();
        }
        handheld->DisableConfiguration();
        player_one->DisableConfiguration();
        handheld->SaveCurrentConfig();
        player_one->SaveCurrentConfig();
    } else {
        auto* controller = hid_core.GetEmulatedControllerByIndex(player_index);
        controller->EnableConfiguration();
        if (connected) {
            controller->Connect();
        } else {
            controller->Disconnect();
        }
        controller->DisableConfiguration();
        controller->SaveCurrentConfig();
    }
}

extern "C" {

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isHandheldOnly(JNIEnv* env,
                                                                           jobject j_obj) {
    return IsHandheldOnly();
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadButtonEvent(
    JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_button_id, jint j_action) {
    EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetButtonState(
        Common::Android::GetJString(env, j_guid), j_port, j_button_id, j_action != 0);
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadAxisEvent(
    JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_stick_id, jfloat j_value) {
    EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetAxisPosition(
        Common::Android::GetJString(env, j_guid), j_port, j_stick_id, j_value);
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadMotionEvent(
    JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jlong j_delta_timestamp,
    jfloat j_x_gyro, jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel,
    jfloat j_z_accel) {
    EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetMotionState(
        Common::Android::GetJString(env, j_guid), j_port, j_delta_timestamp, j_x_gyro, j_y_gyro,
        j_z_gyro, j_x_accel, j_y_accel, j_z_accel);
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onReadNfcTag(JNIEnv* env, jobject j_obj,
                                                                     jbyteArray j_data) {
    jboolean isCopy{false};
    std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
                       static_cast<size_t>(env->GetArrayLength(j_data)));

    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->LoadAmiibo(data);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onRemoveNfcTag(JNIEnv* env, jobject j_obj) {
    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->CloseAmiibo();
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchPressed(JNIEnv* env, jobject j_obj,
                                                                       jint j_id, jfloat j_x_axis,
                                                                       jfloat j_y_axis) {
    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(
            j_id, j_x_axis, j_y_axis);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchMoved(JNIEnv* env, jobject j_obj,
                                                                     jint j_id, jfloat j_x_axis,
                                                                     jfloat j_y_axis) {
    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(
            j_id, j_x_axis, j_y_axis);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchReleased(JNIEnv* env, jobject j_obj,
                                                                        jint j_id) {
    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(j_id);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayButtonEventImpl(
    JNIEnv* env, jobject j_obj, jint j_port, jint j_button_id, jint j_action) {
    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetButtonState(
            j_port, j_button_id, j_action == 1);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayJoystickEventImpl(
    JNIEnv* env, jobject j_obj, jint j_port, jint j_stick_id, jfloat j_x_axis, jfloat j_y_axis) {
    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetStickPosition(
            j_port, j_stick_id, j_x_axis, j_y_axis);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onDeviceMotionEvent(
    JNIEnv* env, jobject j_obj, jint j_port, jlong j_delta_timestamp, jfloat j_x_gyro,
    jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, jfloat j_z_accel) {
    if (EmulationSession::GetInstance().IsRunning()) {
        EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetMotionState(
            j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, j_z_gyro, j_x_accel, j_y_accel,
            j_z_accel);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_reloadInputDevices(JNIEnv* env,
                                                                           jobject j_obj) {
    EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices();
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_registerController(JNIEnv* env,
                                                                           jobject j_obj,
                                                                           jobject j_device) {
    EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->RegisterController(j_device);
}

jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputDevices(JNIEnv* env,
                                                                                jobject j_obj) {
    auto devices = EmulationSession::GetInstance().GetInputSubsystem().GetInputDevices();
    jobjectArray jdevices = env->NewObjectArray(devices.size(), Common::Android::GetStringClass(),
                                                Common::Android::ToJString(env, ""));
    for (size_t i = 0; i < devices.size(); ++i) {
        env->SetObjectArrayElement(jdevices, i,
                                   Common::Android::ToJString(env, devices[i].Serialize()));
    }
    return jdevices;
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadInputProfiles(JNIEnv* env,
                                                                          jobject j_obj) {
    map_profiles.clear();
    const auto input_profile_loc =
        Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "input";

    if (Common::FS::IsDir(input_profile_loc)) {
        Common::FS::IterateDirEntries(
            input_profile_loc,
            [&](const std::filesystem::path& full_path) {
                const auto filename = full_path.filename();
                const auto name_without_ext =
                    Common::FS::PathToUTF8String(GetNameWithoutExtension(filename));

                if (filename.extension() == ".ini" && IsProfileNameValid(name_without_ext)) {
                    map_profiles.insert_or_assign(
                        name_without_ext, std::make_unique<AndroidConfig>(
                                              name_without_ext, Config::ConfigType::InputProfile));
                }

                return true;
            },
            Common::FS::DirEntryFilter::File);
    }
}

jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputProfileNames(
    JNIEnv* env, jobject j_obj) {
    std::vector<std::string> profile_names;
    profile_names.reserve(map_profiles.size());

    auto it = map_profiles.cbegin();
    while (it != map_profiles.cend()) {
        const auto& [profile_name, config] = *it;
        if (!ProfileExistsInFilesystem(profile_name)) {
            it = map_profiles.erase(it);
            continue;
        }

        profile_names.push_back(profile_name);
        ++it;
    }

    std::stable_sort(profile_names.begin(), profile_names.end());

    jobjectArray j_profile_names =
        env->NewObjectArray(profile_names.size(), Common::Android::GetStringClass(),
                            Common::Android::ToJString(env, ""));
    for (size_t i = 0; i < profile_names.size(); ++i) {
        env->SetObjectArrayElement(j_profile_names, i,
                                   Common::Android::ToJString(env, profile_names[i]));
    }

    return j_profile_names;
}

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isProfileNameValid(JNIEnv* env,
                                                                               jobject j_obj,
                                                                               jstring j_name) {
    return Common::Android::GetJString(env, j_name).find_first_of("<>:;\"/\\|,.!?*") ==
           std::string::npos;
}

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_createProfile(JNIEnv* env,
                                                                          jobject j_obj,
                                                                          jstring j_name,
                                                                          jint j_player_index) {
    auto profile_name = Common::Android::GetJString(env, j_name);
    if (ProfileExistsInMap(profile_name)) {
        return false;
    }

    map_profiles.insert_or_assign(
        profile_name,
        std::make_unique<AndroidConfig>(profile_name, Config::ConfigType::InputProfile));

    return SaveProfile(profile_name, j_player_index);
}

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_deleteProfile(JNIEnv* env,
                                                                          jobject j_obj,
                                                                          jstring j_name,
                                                                          jint j_player_index) {
    auto profile_name = Common::Android::GetJString(env, j_name);
    if (!ProfileExistsInMap(profile_name)) {
        return false;
    }

    if (!ProfileExistsInFilesystem(profile_name) ||
        Common::FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) {
        map_profiles.erase(profile_name);
    }

    Settings::values.players.GetValue()[j_player_index].profile_name = "";
    return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name);
}

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadProfile(JNIEnv* env, jobject j_obj,
                                                                        jstring j_name,
                                                                        jint j_player_index) {
    auto profile_name = Common::Android::GetJString(env, j_name);
    return LoadProfile(profile_name, j_player_index);
}

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_saveProfile(JNIEnv* env, jobject j_obj,
                                                                        jstring j_name,
                                                                        jint j_player_index) {
    auto profile_name = Common::Android::GetJString(env, j_name);
    return SaveProfile(profile_name, j_player_index);
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadPerGameConfiguration(
    JNIEnv* env, jobject j_obj, jint j_player_index, jint j_selected_index,
    jstring j_selected_profile_name) {
    static constexpr size_t HANDHELD_INDEX = 8;

    auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
    Settings::values.players.SetGlobal(false);

    auto profile_name = Common::Android::GetJString(env, j_selected_profile_name);
    auto* emulated_controller = hid_core.GetEmulatedControllerByIndex(j_player_index);

    if (j_selected_index == 0) {
        Settings::values.players.GetValue()[j_player_index].profile_name = "";
        if (j_player_index == 0) {
            Settings::values.players.GetValue()[HANDHELD_INDEX] = {};
        }
        Settings::values.players.SetGlobal(true);
        emulated_controller->ReloadFromSettings();
        return;
    }
    if (profile_name.empty()) {
        return;
    }
    auto& player = Settings::values.players.GetValue()[j_player_index];
    auto& global_player = Settings::values.players.GetValue(true)[j_player_index];
    player.profile_name = profile_name;
    global_player.profile_name = profile_name;
    // Read from the profile into the custom player settings
    LoadProfile(profile_name, j_player_index);
    // Make sure the controller is connected
    player.connected = true;

    emulated_controller->ReloadFromSettings();

    if (j_player_index > 0) {
        return;
    }
    // Handle Handheld cases
    auto& handheld_player = Settings::values.players.GetValue()[HANDHELD_INDEX];
    auto* handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
    if (player.controller_type == Settings::ControllerType::Handheld) {
        handheld_player = player;
    } else {
        handheld_player = {};
    }
    handheld_controller->ReloadFromSettings();
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_beginMapping(JNIEnv* env, jobject j_obj,
                                                                     jint jtype) {
    EmulationSession::GetInstance().GetInputSubsystem().BeginMapping(
        static_cast<InputCommon::Polling::InputType>(jtype));
}

jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getNextInput(JNIEnv* env,
                                                                        jobject j_obj) {
    return Common::Android::ToJString(
        env, EmulationSession::GetInstance().GetInputSubsystem().GetNextInput().Serialize());
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_stopMapping(JNIEnv* env, jobject j_obj) {
    EmulationSession::GetInstance().GetInputSubsystem().StopMapping();
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_updateMappingsWithDefaultImpl(
    JNIEnv* env, jobject j_obj, jint j_player_index, jstring j_device_params,
    jstring j_display_name) {
    auto& input_subsystem = EmulationSession::GetInstance().GetInputSubsystem();

    // Clear all previous mappings
    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
        ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
            controller->SetButtonParam(button_id, {});
        });
    }
    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
        ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
            controller->SetStickParam(analog_id, {});
        });
    }

    // Apply new mappings
    auto device = Common::ParamPackage(Common::Android::GetJString(env, j_device_params));
    auto button_mappings = input_subsystem.GetButtonMappingForDevice(device);
    auto analog_mappings = input_subsystem.GetAnalogMappingForDevice(device);
    auto display_name = Common::Android::GetJString(env, j_display_name);
    for (const auto& button_mapping : button_mappings) {
        const std::size_t index = button_mapping.first;
        auto named_mapping = button_mapping.second;
        named_mapping.Set("display", display_name);
        ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
            controller->SetButtonParam(index, named_mapping);
        });
    }
    for (const auto& analog_mapping : analog_mappings) {
        const std::size_t index = analog_mapping.first;
        auto named_mapping = analog_mapping.second;
        named_mapping.Set("display", display_name);
        ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
            controller->SetStickParam(index, named_mapping);
        });
    }
}

jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonParamImpl(JNIEnv* env,
                                                                              jobject j_obj,
                                                                              jint j_player_index,
                                                                              jint j_button) {
    return Common::Android::ToJString(env, EmulationSession::GetInstance()
                                               .System()
                                               .HIDCore()
                                               .GetEmulatedControllerByIndex(j_player_index)
                                               ->GetButtonParam(j_button)
                                               .Serialize());
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setButtonParamImpl(
    JNIEnv* env, jobject j_obj, jint j_player_index, jint j_button_id, jstring j_param) {
    ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
        controller->SetButtonParam(j_button_id,
                                   Common::ParamPackage(Common::Android::GetJString(env, j_param)));
    });
}

jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStickParamImpl(JNIEnv* env,
                                                                             jobject j_obj,
                                                                             jint j_player_index,
                                                                             jint j_stick) {
    return Common::Android::ToJString(env, EmulationSession::GetInstance()
                                               .System()
                                               .HIDCore()
                                               .GetEmulatedControllerByIndex(j_player_index)
                                               ->GetStickParam(j_stick)
                                               .Serialize());
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStickParamImpl(
    JNIEnv* env, jobject j_obj, jint j_player_index, jint j_stick_id, jstring j_param) {
    ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
        controller->SetStickParam(j_stick_id,
                                  Common::ParamPackage(Common::Android::GetJString(env, j_param)));
    });
}

jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonNameImpl(JNIEnv* env,
                                                                          jobject j_obj,
                                                                          jstring j_param) {
    return static_cast<jint>(EmulationSession::GetInstance().GetInputSubsystem().GetButtonName(
        Common::ParamPackage(Common::Android::GetJString(env, j_param))));
}

jintArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getSupportedStyleTagsImpl(
    JNIEnv* env, jobject j_obj, jint j_player_index) {
    auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
    const auto npad_style_set = hid_core.GetSupportedStyleTag();
    std::vector<s32> supported_indexes;
    if (npad_style_set.fullkey == 1) {
        supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::Fullkey));
    }

    if (npad_style_set.joycon_dual == 1) {
        supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconDual));
    }

    if (npad_style_set.joycon_left == 1) {
        supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconLeft));
    }

    if (npad_style_set.joycon_right == 1) {
        supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconRight));
    }

    if (j_player_index == 0 && npad_style_set.handheld == 1) {
        supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::Handheld));
    }

    if (npad_style_set.gamecube == 1) {
        supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::GameCube));
    }

    jintArray j_supported_indexes = env->NewIntArray(supported_indexes.size());
    env->SetIntArrayRegion(j_supported_indexes, 0, supported_indexes.size(),
                           supported_indexes.data());
    return j_supported_indexes;
}

jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStyleIndexImpl(JNIEnv* env,
                                                                          jobject j_obj,
                                                                          jint j_player_index) {
    return static_cast<s32>(EmulationSession::GetInstance()
                                .System()
                                .HIDCore()
                                .GetEmulatedControllerByIndex(j_player_index)
                                ->GetNpadStyleIndex(true));
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStyleIndexImpl(JNIEnv* env,
                                                                          jobject j_obj,
                                                                          jint j_player_index,
                                                                          jint j_style_index) {
    auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
    auto type = static_cast<Core::HID::NpadStyleIndex>(j_style_index);
    ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
        controller->SetNpadStyleIndex(type);
    });
    if (j_player_index == 0) {
        auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
        auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
        ConnectController(j_player_index,
                          player_one->IsConnected(true) || handheld->IsConnected(true));
    }
}

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isControllerImpl(JNIEnv* env,
                                                                             jobject j_obj,
                                                                             jstring jparams) {
    return static_cast<jint>(EmulationSession::GetInstance().GetInputSubsystem().IsController(
        Common::ParamPackage(Common::Android::GetJString(env, jparams))));
}

jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getIsConnected(JNIEnv* env,
                                                                           jobject j_obj,
                                                                           jint j_player_index) {
    auto& hid_core = EmulationSession::GetInstance().System().HIDCore();
    auto* controller = hid_core.GetEmulatedControllerByIndex(static_cast<size_t>(j_player_index));
    if (j_player_index == 0 &&
        controller->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) {
        return hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld)->IsConnected(true);
    }
    return controller->IsConnected(true);
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_connectControllersImpl(
    JNIEnv* env, jobject j_obj, jbooleanArray j_connected) {
    jboolean isCopy = false;
    auto j_connected_array_size = env->GetArrayLength(j_connected);
    jboolean* j_connected_array = env->GetBooleanArrayElements(j_connected, &isCopy);
    for (int i = 0; i < j_connected_array_size; ++i) {
        ConnectController(i, j_connected_array[i]);
    }
}

void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_resetControllerMappings(
    JNIEnv* env, jobject j_obj, jint j_player_index) {
    // Clear all previous mappings
    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
        ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
            controller->SetButtonParam(button_id, {});
        });
    }
    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
        ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) {
            controller->SetStickParam(analog_id, {});
        });
    }
}

} // extern "C"
