// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include "core/core.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/service/hid/controllers/applet_resource.h"
#include "core/hle/service/hid/controllers/types/shared_memory_format.h"
#include "core/hle/service/hid/errors.h"

namespace Service::HID {

AppletResource::AppletResource(Core::System& system_) : system{system_} {}

AppletResource::~AppletResource() = default;

Result AppletResource::CreateAppletResource(u64 aruid) {
    const u64 index = GetIndexFromAruid(aruid);

    if (index >= AruidIndexMax) {
        return ResultAruidNotRegistered;
    }

    if (data[index].flag.is_assigned) {
        return ResultAruidAlreadyRegistered;
    }

    auto& shared_memory = shared_memory_holder[index];
    if (!shared_memory.IsMapped()) {
        const Result result = shared_memory.Initialize(system);
        if (result.IsError()) {
            return result;
        }
        if (shared_memory.GetAddress() == nullptr) {
            shared_memory.Finalize();
            return ResultSharedMemoryNotInitialized;
        }
    }

    auto* shared_memory_format = shared_memory.GetAddress();
    if (shared_memory_format != nullptr) {
        shared_memory_format->Initialize();
    }

    data[index].shared_memory_format = shared_memory_format;
    data[index].flag.is_assigned.Assign(true);
    // TODO: InitializeSixAxisControllerConfig(false);
    active_aruid = aruid;
    return ResultSuccess;
}

Result AppletResource::RegisterAppletResourceUserId(u64 aruid, bool enable_input) {
    const u64 index = GetIndexFromAruid(aruid);

    if (index < AruidIndexMax) {
        return ResultAruidAlreadyRegistered;
    }

    std::size_t data_index = AruidIndexMax;
    for (std::size_t i = 0; i < AruidIndexMax; i++) {
        if (!data[i].flag.is_initialized) {
            data_index = i;
            break;
        }
    }

    if (data_index == AruidIndexMax) {
        return ResultAruidNoAvailableEntries;
    }

    AruidData& aruid_data = data[data_index];

    aruid_data.aruid = aruid;
    aruid_data.flag.is_initialized.Assign(true);
    if (enable_input) {
        aruid_data.flag.enable_pad_input.Assign(true);
        aruid_data.flag.enable_six_axis_sensor.Assign(true);
        aruid_data.flag.bit_18.Assign(true);
        aruid_data.flag.enable_touchscreen.Assign(true);
    }

    data_index = AruidIndexMax;
    for (std::size_t i = 0; i < AruidIndexMax; i++) {
        if (registration_list.flag[i] == RegistrationStatus::Initialized) {
            if (registration_list.aruid[i] != aruid) {
                continue;
            }
            data_index = i;
            break;
        }
        if (registration_list.flag[i] == RegistrationStatus::None) {
            data_index = i;
            break;
        }
    }

    if (data_index == AruidIndexMax) {
        return ResultSuccess;
    }

    registration_list.flag[data_index] = RegistrationStatus::Initialized;
    registration_list.aruid[data_index] = aruid;

    return ResultSuccess;
}

void AppletResource::UnregisterAppletResourceUserId(u64 aruid) {
    u64 index = GetIndexFromAruid(aruid);

    if (index < AruidIndexMax) {
        if (data[index].flag.is_assigned) {
            data[index].shared_memory_format = nullptr;
            data[index].flag.is_assigned.Assign(false);
        }
    }

    index = GetIndexFromAruid(aruid);
    if (index < AruidIndexMax) {
        DestroySevenSixAxisTransferMemory();
        data[index].flag.raw = 0;
        data[index].aruid = 0;

        index = GetIndexFromAruid(aruid);
        if (index < AruidIndexMax) {
            registration_list.flag[index] = RegistrationStatus::PendingDelete;
        }
    }
}

void AppletResource::FreeAppletResourceId(u64 aruid) {
    u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return;
    }

    auto& aruid_data = data[index];
    if (aruid_data.flag.is_assigned) {
        aruid_data.shared_memory_format = nullptr;
        aruid_data.flag.is_assigned.Assign(false);
    }
}

u64 AppletResource::GetActiveAruid() {
    return active_aruid;
}

Result AppletResource::GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, u64 aruid) {
    u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return ResultAruidNotRegistered;
    }

    *out_handle = shared_memory_holder[index].GetHandle();
    return ResultSuccess;
}

Result AppletResource::GetSharedMemoryFormat(SharedMemoryFormat** out_shared_memory_format,
                                             u64 aruid) {
    u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return ResultAruidNotRegistered;
    }

    *out_shared_memory_format = data[index].shared_memory_format;
    return ResultSuccess;
}

AruidData* AppletResource::GetAruidData(u64 aruid) {
    const u64 aruid_index = GetIndexFromAruid(aruid);
    if (aruid_index == AruidIndexMax) {
        return nullptr;
    }
    return &data[aruid_index];
}

AruidData* AppletResource::GetAruidDataByIndex(std::size_t aruid_index) {
    return &data[aruid_index];
}

bool AppletResource::IsVibrationAruidActive(u64 aruid) const {
    return aruid == 0 || aruid == active_vibration_aruid;
}

u64 AppletResource::GetIndexFromAruid(u64 aruid) {
    for (std::size_t i = 0; i < AruidIndexMax; i++) {
        if (registration_list.flag[i] == RegistrationStatus::Initialized &&
            registration_list.aruid[i] == aruid) {
            return i;
        }
    }
    return AruidIndexMax;
}

Result AppletResource::DestroySevenSixAxisTransferMemory() {
    // TODO
    return ResultSuccess;
}

void AppletResource::EnableInput(u64 aruid, bool is_enabled) {
    const u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return;
    }

    data[index].flag.enable_pad_input.Assign(is_enabled);
    data[index].flag.enable_touchscreen.Assign(is_enabled);
}

void AppletResource::EnableSixAxisSensor(u64 aruid, bool is_enabled) {
    const u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return;
    }

    data[index].flag.enable_six_axis_sensor.Assign(is_enabled);
}

void AppletResource::EnablePadInput(u64 aruid, bool is_enabled) {
    const u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return;
    }

    data[index].flag.enable_pad_input.Assign(is_enabled);
}

void AppletResource::EnableTouchScreen(u64 aruid, bool is_enabled) {
    const u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return;
    }

    data[index].flag.enable_touchscreen.Assign(is_enabled);
}

void AppletResource::SetIsPalmaConnectable(u64 aruid, bool is_connectable) {
    const u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return;
    }

    data[index].flag.is_palma_connectable.Assign(is_connectable);
}

void AppletResource::EnablePalmaBoostMode(u64 aruid, bool is_enabled) {
    const u64 index = GetIndexFromAruid(aruid);
    if (index >= AruidIndexMax) {
        return;
    }

    data[index].flag.enable_palma_boost_mode.Assign(is_enabled);
}

Result AppletResource::RegisterCoreAppletResource() {
    if (ref_counter == std::numeric_limits<s32>::max() - 1) {
        return ResultAppletResourceOverflow;
    }
    if (ref_counter == 0) {
        const u64 index = GetIndexFromAruid(0);
        if (index < AruidIndexMax) {
            return ResultAruidAlreadyRegistered;
        }

        std::size_t data_index = AruidIndexMax;
        for (std::size_t i = 0; i < AruidIndexMax; i++) {
            if (!data[i].flag.is_initialized) {
                data_index = i;
                break;
            }
        }

        if (data_index == AruidIndexMax) {
            return ResultAruidNoAvailableEntries;
        }

        AruidData& aruid_data = data[data_index];

        aruid_data.aruid = 0;
        aruid_data.flag.is_initialized.Assign(true);
        aruid_data.flag.enable_pad_input.Assign(true);
        aruid_data.flag.enable_six_axis_sensor.Assign(true);
        aruid_data.flag.bit_18.Assign(true);
        aruid_data.flag.enable_touchscreen.Assign(true);

        data_index = AruidIndexMax;
        for (std::size_t i = 0; i < AruidIndexMax; i++) {
            if (registration_list.flag[i] == RegistrationStatus::Initialized) {
                if (registration_list.aruid[i] != 0) {
                    continue;
                }
                data_index = i;
                break;
            }
            if (registration_list.flag[i] == RegistrationStatus::None) {
                data_index = i;
                break;
            }
        }

        Result result = ResultSuccess;

        if (data_index == AruidIndexMax) {
            result = CreateAppletResource(0);
        } else {
            registration_list.flag[data_index] = RegistrationStatus::Initialized;
            registration_list.aruid[data_index] = 0;
        }

        if (result.IsError()) {
            UnregisterAppletResourceUserId(0);
            return result;
        }
    }
    ref_counter++;
    return ResultSuccess;
}

Result AppletResource::UnregisterCoreAppletResource() {
    if (ref_counter == 0) {
        return ResultAppletResourceNotInitialized;
    }

    if (--ref_counter == 0) {
        UnregisterAppletResourceUserId(0);
    }

    return ResultSuccess;
}

} // namespace Service::HID
