// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <memory>

#include "common/logging/log.h"
#include "core/file_sys/control_metadata.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/glue/arp.h"
#include "core/hle/service/glue/errors.h"
#include "core/hle/service/glue/manager.h"
#include "core/hle/service/service.h"

namespace Service::Glue {

namespace {
std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) {
    const auto& list = system.Kernel().GetProcessList();
    const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
        return process->GetProcessID() == process_id;
    });

    if (iter == list.end()) {
        return std::nullopt;
    }

    return (*iter)->GetTitleID();
}
} // Anonymous namespace

ARP_R::ARP_R(const Core::System& system, const ARPManager& manager)
    : ServiceFramework{"arp:r"}, system(system), manager(manager) {
    // clang-format off
        static const FunctionInfo functions[] = {
            {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"},
            {1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"},
            {2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"},
            {3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"},
        };
    // clang-format on

    RegisterHandlers(functions);
}

ARP_R::~ARP_R() = default;

void ARP_R::GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto process_id = rp.PopRaw<u64>();

    LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);

    const auto title_id = GetTitleIDForProcessID(system, process_id);
    if (!title_id.has_value()) {
        LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ERR_NOT_REGISTERED);
        return;
    }

    const auto res = manager.GetLaunchProperty(*title_id);

    if (res.Failed()) {
        LOG_ERROR(Service_ARP, "Failed to get launch property!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(res.Code());
        return;
    }

    IPC::ResponseBuilder rb{ctx, 6};
    rb.Push(RESULT_SUCCESS);
    rb.PushRaw(*res);
}

void ARP_R::GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto title_id = rp.PopRaw<u64>();

    LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);

    const auto res = manager.GetLaunchProperty(title_id);

    if (res.Failed()) {
        LOG_ERROR(Service_ARP, "Failed to get launch property!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(res.Code());
        return;
    }

    IPC::ResponseBuilder rb{ctx, 6};
    rb.Push(RESULT_SUCCESS);
    rb.PushRaw(*res);
}

void ARP_R::GetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto process_id = rp.PopRaw<u64>();

    LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);

    const auto title_id = GetTitleIDForProcessID(system, process_id);
    if (!title_id.has_value()) {
        LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ERR_NOT_REGISTERED);
        return;
    }

    const auto res = manager.GetControlProperty(*title_id);

    if (res.Failed()) {
        LOG_ERROR(Service_ARP, "Failed to get control property!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(res.Code());
        return;
    }

    ctx.WriteBuffer(*res);

    IPC::ResponseBuilder rb{ctx, 2};
    rb.Push(RESULT_SUCCESS);
}

void ARP_R::GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto title_id = rp.PopRaw<u64>();

    LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);

    const auto res = manager.GetControlProperty(title_id);

    if (res.Failed()) {
        LOG_ERROR(Service_ARP, "Failed to get control property!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(res.Code());
        return;
    }

    ctx.WriteBuffer(*res);

    IPC::ResponseBuilder rb{ctx, 2};
    rb.Push(RESULT_SUCCESS);
}

class IRegistrar final : public ServiceFramework<IRegistrar> {
    friend class ARP_W;

public:
    explicit IRegistrar(
        std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer)
        : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) {
        // clang-format off
        static const FunctionInfo functions[] = {
            {0, &IRegistrar::Issue, "Issue"},
            {1, &IRegistrar::SetApplicationLaunchProperty, "SetApplicationLaunchProperty"},
            {2, &IRegistrar::SetApplicationControlProperty, "SetApplicationControlProperty"},
        };
        // clang-format on

        RegisterHandlers(functions);
    }

private:
    void Issue(Kernel::HLERequestContext& ctx) {
        IPC::RequestParser rp{ctx};
        const auto process_id = rp.PopRaw<u64>();

        LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);

        if (process_id == 0) {
            LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
            IPC::ResponseBuilder rb{ctx, 2};
            rb.Push(ERR_INVALID_PROCESS_ID);
            return;
        }

        if (issued) {
            LOG_ERROR(Service_ARP,
                      "Attempted to issue registrar, but registrar is already issued!");
            IPC::ResponseBuilder rb{ctx, 2};
            rb.Push(ERR_INVALID_ACCESS);
            return;
        }

        issue_process_id(process_id, launch, std::move(control));
        issued = true;

        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(RESULT_SUCCESS);
    }

    void SetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
        LOG_DEBUG(Service_ARP, "called");

        if (issued) {
            LOG_ERROR(
                Service_ARP,
                "Attempted to set application launch property, but registrar is already issued!");
            IPC::ResponseBuilder rb{ctx, 2};
            rb.Push(ERR_INVALID_ACCESS);
            return;
        }

        IPC::RequestParser rp{ctx};
        launch = rp.PopRaw<ApplicationLaunchProperty>();

        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(RESULT_SUCCESS);
    }

    void SetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
        LOG_DEBUG(Service_ARP, "called");

        if (issued) {
            LOG_ERROR(
                Service_ARP,
                "Attempted to set application control property, but registrar is already issued!");
            IPC::ResponseBuilder rb{ctx, 2};
            rb.Push(ERR_INVALID_ACCESS);
            return;
        }

        control = ctx.ReadBuffer();

        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(RESULT_SUCCESS);
    }

    std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issue_process_id;
    bool issued = false;
    ApplicationLaunchProperty launch;
    std::vector<u8> control;
};

ARP_W::ARP_W(const Core::System& system, ARPManager& manager)
    : ServiceFramework{"arp:w"}, system(system), manager(manager) {
    // clang-format off
        static const FunctionInfo functions[] = {
            {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
            {1, &ARP_W::DeleteProperties, "DeleteProperties"},
        };
    // clang-format on

    RegisterHandlers(functions);
}

ARP_W::~ARP_W() = default;

void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) {
    LOG_DEBUG(Service_ARP, "called");

    registrar = std::make_shared<IRegistrar>(
        [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) {
            const auto res = GetTitleIDForProcessID(system, process_id);
            if (!res.has_value()) {
                return ERR_NOT_REGISTERED;
            }

            return manager.Register(*res, launch, std::move(control));
        });

    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
    rb.Push(RESULT_SUCCESS);
    rb.PushIpcInterface(registrar);
}

void ARP_W::DeleteProperties(Kernel::HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto process_id = rp.PopRaw<u64>();

    LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);

    if (process_id == 0) {
        LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ERR_INVALID_PROCESS_ID);
        return;
    }

    const auto title_id = GetTitleIDForProcessID(system, process_id);

    if (!title_id.has_value()) {
        LOG_ERROR(Service_ARP, "No title ID for process ID!");
        IPC::ResponseBuilder rb{ctx, 2};
        rb.Push(ERR_NOT_REGISTERED);
        return;
    }

    IPC::ResponseBuilder rb{ctx, 2};
    rb.Push(manager.Unregister(*title_id));
}

} // namespace Service::Glue
