/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2021, Google Inc.
 *
 * Allocate FrameBuffer using gralloc API
 */

#include <dlfcn.h>
#include <memory>
#include <vector>

#include <libcamera/base/log.h>
#include <libcamera/base/shared_fd.h>

#include "libcamera/internal/formats.h"
#include "libcamera/internal/framebuffer.h"

#include <hardware/camera3.h>
#include <hardware/gralloc.h>
#include <hardware/hardware.h>

#include "../camera_device.h"
#include "../frame_buffer_allocator.h"
#include "../hal_framebuffer.h"

using namespace libcamera;

LOG_DECLARE_CATEGORY(HAL)

namespace {
class GenericFrameBufferData : public FrameBuffer::Private
{
	LIBCAMERA_DECLARE_PUBLIC(FrameBuffer)

public:
	GenericFrameBufferData(struct alloc_device_t *allocDevice,
			       buffer_handle_t handle,
			       const std::vector<FrameBuffer::Plane> &planes)
		: FrameBuffer::Private(planes), allocDevice_(allocDevice),
		  handle_(handle)
	{
		ASSERT(allocDevice_);
		ASSERT(handle_);
	}

	~GenericFrameBufferData() override
	{
		/*
		 * allocDevice_ is used to destroy handle_. allocDevice_ is
		 * owned by PlatformFrameBufferAllocator::Private.
		 * GenericFrameBufferData must be destroyed before it is
		 * destroyed.
		 *
		 * \todo Consider managing alloc_device_t with std::shared_ptr
		 * if this is difficult to maintain.
		 *
		 * \todo Thread safety against alloc_device_t is not documented.
		 * Is it no problem to call alloc/free in parallel?
		 */
		allocDevice_->free(allocDevice_, handle_);
	}

private:
	struct alloc_device_t *allocDevice_;
	const buffer_handle_t handle_;
};
} /* namespace */

class PlatformFrameBufferAllocator::Private : public Extensible::Private
{
	LIBCAMERA_DECLARE_PUBLIC(PlatformFrameBufferAllocator)

public:
	Private(CameraDevice *const cameraDevice)
		: cameraDevice_(cameraDevice),
		  hardwareModule_(nullptr),
		  allocDevice_(nullptr)
	{
		hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &hardwareModule_);
		ASSERT(hardwareModule_);
	}

	~Private() override;

	std::unique_ptr<HALFrameBuffer>
	allocate(int halPixelFormat, const libcamera::Size &size, uint32_t usage);

private:
	const CameraDevice *const cameraDevice_;
	const struct hw_module_t *hardwareModule_;
	struct alloc_device_t *allocDevice_;
};

PlatformFrameBufferAllocator::Private::~Private()
{
	if (allocDevice_)
		gralloc_close(allocDevice_);
	dlclose(hardwareModule_->dso);
}

std::unique_ptr<HALFrameBuffer>
PlatformFrameBufferAllocator::Private::allocate(int halPixelFormat,
						const libcamera::Size &size,
						uint32_t usage)
{
	if (!allocDevice_) {
		int ret = gralloc_open(hardwareModule_, &allocDevice_);
		if (ret) {
			LOG(HAL, Fatal) << "gralloc_open() failed: " << ret;
			return nullptr;
		}
	}

	int stride = 0;
	buffer_handle_t handle = nullptr;
	int ret = allocDevice_->alloc(allocDevice_, size.width, size.height,
				      halPixelFormat, usage, &handle, &stride);
	if (ret) {
		LOG(HAL, Error) << "failed buffer allocation: " << ret;
		return nullptr;
	}
	if (!handle) {
		LOG(HAL, Fatal) << "invalid buffer_handle_t";
		return nullptr;
	}

	/* This code assumes the planes are mapped consecutively. */
	const libcamera::PixelFormat pixelFormat =
		cameraDevice_->capabilities()->toPixelFormat(halPixelFormat);
	const auto &info = PixelFormatInfo::info(pixelFormat);
	std::vector<FrameBuffer::Plane> planes(info.numPlanes());

	SharedFD fd{ handle->data[0] };
	size_t offset = 0;
	for (auto [i, plane] : utils::enumerate(planes)) {
		const size_t planeSize = info.planeSize(size.height, i, stride);

		plane.fd = fd;
		plane.offset = offset;
		plane.length = planeSize;
		offset += planeSize;
	}

	return std::make_unique<HALFrameBuffer>(
		std::make_unique<GenericFrameBufferData>(
			allocDevice_, handle, planes),
		handle);
}

PUBLIC_FRAME_BUFFER_ALLOCATOR_IMPLEMENTATION
