/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2020, Google Inc.
 *
 * camera_stream.cpp - Camera HAL stream
 */

#include "camera_stream.h"

#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <unistd.h>

#include <libcamera/formats.h>

#include "jpeg/post_processor_jpeg.h"
#include "yuv/post_processor_yuv.h"

#include "camera_buffer.h"
#include "camera_capabilities.h"
#include "camera_device.h"
#include "camera_metadata.h"
#include "post_processor.h"

using namespace libcamera;

LOG_DECLARE_CATEGORY(HAL)

/*
 * \class CameraStream
 * \brief Map a camera3_stream_t to a StreamConfiguration
 *
 * The CameraStream class maps a camera3_stream_t provided by Android
 * camera framework to a libcamera::StreamConfiguration.
 *
 * The StreamConfiguration is represented by its index as recorded in the
 * CameraConfiguration and not by pointer as StreamConfiguration is subject to
 * relocation.
 *
 * A single StreamConfiguration may be used to deliver one or more streams to
 * the Android framework. The mapping type between a camera3 stream to a
 * StreamConfiguration is described by the CameraStream::Type.
 *
 * CameraStream handles all the aspects of producing a stream with the size
 * and format requested by the camera3 stream from the data produced by
 * the associated libcamera::Stream, including the creation of the encoder
 * and buffer allocation.
 */

CameraStream::CameraStream(CameraDevice *const cameraDevice,
			   CameraConfiguration *config, Type type,
			   camera3_stream_t *camera3Stream, unsigned int index)
	: cameraDevice_(cameraDevice), config_(config), type_(type),
	  camera3Stream_(camera3Stream), index_(index)
{
}

CameraStream::CameraStream(CameraStream &&other) = default;

CameraStream::~CameraStream() = default;

const StreamConfiguration &CameraStream::configuration() const
{
	return config_->at(index_);
}

Stream *CameraStream::stream() const
{
	return configuration().stream();
}

int CameraStream::configure()
{
	if (type_ == Type::Internal || type_ == Type::Mapped) {
		const PixelFormat outFormat =
			cameraDevice_->capabilities()->toPixelFormat(camera3Stream_->format);
		StreamConfiguration output = configuration();
		output.pixelFormat = outFormat;
		output.size.width = camera3Stream_->width;
		output.size.height = camera3Stream_->height;

		switch (outFormat) {
		case formats::NV12:
			postProcessor_ = std::make_unique<PostProcessorYuv>();
			break;

		case formats::MJPEG:
			postProcessor_ = std::make_unique<PostProcessorJpeg>(cameraDevice_);
			break;

		default:
			LOG(HAL, Error) << "Unsupported format: " << outFormat;
			return -EINVAL;
		}

		int ret = postProcessor_->configure(configuration(), output);
		if (ret)
			return ret;

		worker_ = std::make_unique<PostProcessorWorker>(postProcessor_.get());
		postProcessor_->processComplete.connect(
			this, [&](Camera3RequestDescriptor::StreamBuffer *streamBuffer,
				  PostProcessor::Status status) {
				Camera3RequestDescriptor::Status bufferStatus;

				if (status == PostProcessor::Status::Success)
					bufferStatus = Camera3RequestDescriptor::Status::Success;
				else
					bufferStatus = Camera3RequestDescriptor::Status::Error;

				cameraDevice_->streamProcessingComplete(streamBuffer,
									bufferStatus);
			});

		worker_->start();
	}

	if (type_ == Type::Internal) {
		allocator_ = std::make_unique<FrameBufferAllocator>(cameraDevice_->camera());
		mutex_ = std::make_unique<std::mutex>();

		int ret = allocator_->allocate(stream());
		if (ret < 0)
			return ret;

		/* Save a pointer to the reserved frame buffers */
		for (const auto &frameBuffer : allocator_->buffers(stream()))
			buffers_.push_back(frameBuffer.get());
	}

	camera3Stream_->max_buffers = configuration().bufferCount;

	return 0;
}

int CameraStream::waitFence(int fence)
{
	/*
	 * \todo The implementation here is copied from camera_worker.cpp
	 * and both should be removed once libcamera is instrumented to handle
	 * fences waiting in the core.
	 *
	 * \todo Better characterize the timeout. Currently equal to the one
	 * used by the Rockchip Camera HAL on ChromeOS.
	 */
	constexpr unsigned int timeoutMs = 300;
	struct pollfd fds = { fence, POLLIN, 0 };

	do {
		int ret = poll(&fds, 1, timeoutMs);
		if (ret == 0)
			return -ETIME;

		if (ret > 0) {
			if (fds.revents & (POLLERR | POLLNVAL))
				return -EINVAL;

			return 0;
		}
	} while (errno == EINTR || errno == EAGAIN);

	return -errno;
}

int CameraStream::process(Camera3RequestDescriptor::StreamBuffer *streamBuffer)
{
	ASSERT(type_ != Type::Direct);

	/* Handle waiting on fences on the destination buffer. */
	if (streamBuffer->fence != -1) {
		int ret = waitFence(streamBuffer->fence);
		if (ret < 0) {
			LOG(HAL, Error) << "Failed waiting for fence: "
					<< streamBuffer->fence << ": " << strerror(-ret);
			return ret;
		}

		::close(streamBuffer->fence);
		streamBuffer->fence = -1;
	}

	const StreamConfiguration &output = configuration();
	streamBuffer->dstBuffer = std::make_unique<CameraBuffer>(
		*streamBuffer->camera3Buffer, output.pixelFormat, output.size,
		PROT_READ | PROT_WRITE);
	if (!streamBuffer->dstBuffer->isValid()) {
		LOG(HAL, Error) << "Failed to create destination buffer";
		return -EINVAL;
	}

	worker_->queueRequest(streamBuffer);

	return 0;
}

void CameraStream::flush()
{
	if (!postProcessor_)
		return;

	worker_->flush();
}

FrameBuffer *CameraStream::getBuffer()
{
	if (!allocator_)
		return nullptr;

	std::lock_guard<std::mutex> locker(*mutex_);

	if (buffers_.empty()) {
		LOG(HAL, Error) << "Buffer underrun";
		return nullptr;
	}

	FrameBuffer *buffer = buffers_.back();
	buffers_.pop_back();

	return buffer;
}

void CameraStream::putBuffer(FrameBuffer *buffer)
{
	if (!allocator_)
		return;

	std::lock_guard<std::mutex> locker(*mutex_);

	buffers_.push_back(buffer);
}

CameraStream::PostProcessorWorker::PostProcessorWorker(PostProcessor *postProcessor)
	: postProcessor_(postProcessor)
{
}

CameraStream::PostProcessorWorker::~PostProcessorWorker()
{
	{
		libcamera::MutexLocker lock(mutex_);
		state_ = State::Stopped;
	}

	cv_.notify_one();
	wait();
}

void CameraStream::PostProcessorWorker::start()
{
	{
		libcamera::MutexLocker lock(mutex_);
		ASSERT(state_ != State::Running);
		state_ = State::Running;
	}

	Thread::start();
}

void CameraStream::PostProcessorWorker::queueRequest(Camera3RequestDescriptor::StreamBuffer *dest)
{
	{
		MutexLocker lock(mutex_);
		ASSERT(state_ == State::Running);
		requests_.push(dest);
	}

	cv_.notify_one();
}

void CameraStream::PostProcessorWorker::run()
{
	MutexLocker locker(mutex_);

	while (1) {
		cv_.wait(locker, [&] {
			return state_ != State::Running || !requests_.empty();
		});

		if (state_ != State::Running)
			break;

		Camera3RequestDescriptor::StreamBuffer *streamBuffer = requests_.front();
		requests_.pop();
		locker.unlock();

		postProcessor_->process(streamBuffer);

		locker.lock();
	}

	if (state_ == State::Flushing) {
		std::queue<Camera3RequestDescriptor::StreamBuffer *> requests =
			std::move(requests_);
		locker.unlock();

		while (!requests.empty()) {
			postProcessor_->processComplete.emit(
				requests.front(), PostProcessor::Status::Error);
			requests.pop();
		}

		locker.lock();
		state_ = State::Stopped;
	}
}

void CameraStream::PostProcessorWorker::flush()
{
	libcamera::MutexLocker lock(mutex_);
	state_ = State::Flushing;
	lock.unlock();

	cv_.notify_one();
}