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

#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"
#include "core/hle/service/nvnflinger/display.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/surface_flinger.h"
#include "core/hle/service/sm/sm.h"

#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"

namespace Service::Nvnflinger {

SurfaceFlinger::SurfaceFlinger(Core::System& system, HosBinderDriverServer& server)
    : m_system(system), m_server(server), m_context(m_system, "SurfaceFlinger") {
    nvdrv = m_system.ServiceManager().GetService<Nvidia::NVDRV>("nvdrv:s", true)->GetModule();
    disp_fd = nvdrv->Open("/dev/nvdisp_disp0", {});
}

SurfaceFlinger::~SurfaceFlinger() {
    nvdrv->Close(disp_fd);
}

void SurfaceFlinger::AddDisplay(u64 display_id) {
    m_displays.emplace_back(display_id);
}

void SurfaceFlinger::RemoveDisplay(u64 display_id) {
    std::erase_if(m_displays, [&](auto& display) { return display.id == display_id; });
}

bool SurfaceFlinger::ComposeDisplay(s32* out_swap_interval, f32* out_compose_speed_scale,
                                    u64 display_id) {
    auto* const display = this->FindDisplay(display_id);
    if (!display || !display->HasLayers()) {
        return false;
    }

    *out_swap_interval =
        m_composer.ComposeLocked(out_compose_speed_scale, *display,
                                 *nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd));
    return true;
}

void SurfaceFlinger::AddLayerToDisplayStack(u64 display_id, s32 consumer_binder_id) {
    auto* const display = this->FindDisplay(display_id);
    auto binder = std::static_pointer_cast<android::BufferQueueConsumer>(
        m_server.TryGetBinder(consumer_binder_id));

    if (!display || !binder) {
        return;
    }

    auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(binder));
    buffer_item_consumer->Connect(false);

    display->stack.layers.emplace_back(std::move(buffer_item_consumer), consumer_binder_id);
}

void SurfaceFlinger::RemoveLayerFromDisplayStack(u64 display_id, s32 consumer_binder_id) {
    auto* const display = this->FindDisplay(display_id);
    if (!display) {
        return;
    }

    m_composer.RemoveLayerLocked(*display, consumer_binder_id);
    std::erase_if(display->stack.layers,
                  [&](auto& layer) { return layer.consumer_id == consumer_binder_id; });
}

void SurfaceFlinger::SetLayerVisibility(s32 consumer_binder_id, bool visible) {
    if (auto* layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
        layer->visible = visible;
        return;
    }
}

void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blending) {
    if (auto* layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
        layer->blending = blending;
        return;
    }
}

Display* SurfaceFlinger::FindDisplay(u64 display_id) {
    for (auto& display : m_displays) {
        if (display.id == display_id) {
            return &display;
        }
    }

    return nullptr;
}

Layer* SurfaceFlinger::FindLayer(s32 consumer_binder_id) {
    for (auto& display : m_displays) {
        if (auto* layer = display.FindLayer(consumer_binder_id); layer != nullptr) {
            return layer;
        }
    }

    return nullptr;
}

void SurfaceFlinger::CreateBufferQueue(s32* out_consumer_binder_id, s32* out_producer_binder_id) {
    auto& nvmap = nvdrv->GetContainer().GetNvMapFile();
    auto core = std::make_shared<android::BufferQueueCore>();
    auto producer = std::make_shared<android::BufferQueueProducer>(m_context, core, nvmap);
    auto consumer = std::make_shared<android::BufferQueueConsumer>(core);

    *out_consumer_binder_id = m_server.RegisterBinder(std::move(consumer));
    *out_producer_binder_id = m_server.RegisterBinder(std::move(producer));
}

void SurfaceFlinger::DestroyBufferQueue(s32 consumer_binder_id, s32 producer_binder_id) {
    m_server.UnregisterBinder(producer_binder_id);
    m_server.UnregisterBinder(consumer_binder_id);
}

} // namespace Service::Nvnflinger
