/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2020-2021, Google Inc.
 *
 * Simple capture helper
 */

#include "capture.h"

#include <assert.h>

#include <gtest/gtest.h>

using namespace libcamera;

Capture::Capture(std::shared_ptr<Camera> camera)
	: camera_(std::move(camera)), allocator_(camera_)
{
}

Capture::~Capture()
{
	stop();
}

void Capture::configure(StreamRole role)
{
	config_ = camera_->generateConfiguration({ role });

	if (!config_)
		GTEST_SKIP() << "Role not supported by camera";

	if (config_->validate() != CameraConfiguration::Valid) {
		config_.reset();
		FAIL() << "Configuration not valid";
	}

	if (camera_->configure(config_.get())) {
		config_.reset();
		FAIL() << "Failed to configure camera";
	}
}

void Capture::run(unsigned int captureLimit, std::optional<unsigned int> queueLimit)
{
	assert(!queueLimit || captureLimit <= *queueLimit);

	captureLimit_ = captureLimit;
	queueLimit_ = queueLimit;

	captureCount_ = queueCount_ = 0;

	EventLoop loop;
	loop_ = &loop;

	start();

	for (const auto &request : requests_)
		queueRequest(request.get());

	EXPECT_EQ(loop_->exec(), 0);

	stop();

	EXPECT_LE(captureLimit_, captureCount_);
	EXPECT_LE(captureCount_, queueCount_);
	EXPECT_TRUE(!queueLimit_ || queueCount_ <= *queueLimit_);
}

int Capture::queueRequest(libcamera::Request *request)
{
	if (queueLimit_ && queueCount_ >= *queueLimit_)
		return 0;

	int ret = camera_->queueRequest(request);
	if (ret < 0)
		return ret;

	queueCount_ += 1;
	return 0;
}

void Capture::requestComplete(Request *request)
{
	captureCount_++;
	if (captureCount_ >= captureLimit_) {
		loop_->exit(0);
		return;
	}

	EXPECT_EQ(request->status(), Request::Status::RequestComplete)
		<< "Request didn't complete successfully";

	request->reuse(Request::ReuseBuffers);
	if (queueRequest(request))
		loop_->exit(-EINVAL);
}

void Capture::start()
{
	assert(config_);
	assert(!config_->empty());
	assert(!allocator_.allocated());
	assert(requests_.empty());

	Stream *stream = config_->at(0).stream();
	int count = allocator_.allocate(stream);

	ASSERT_GE(count, 0) << "Failed to allocate buffers";
	EXPECT_EQ(count, config_->at(0).bufferCount) << "Allocated less buffers than expected";

	const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator_.buffers(stream);

	/* No point in testing less requests then the camera depth. */
	if (queueLimit_ && *queueLimit_ < buffers.size()) {
		GTEST_SKIP() << "Camera needs " << buffers.size()
			     << " requests, can't test only " << *queueLimit_;
	}

	for (const std::unique_ptr<FrameBuffer> &buffer : buffers) {
		std::unique_ptr<Request> request = camera_->createRequest();
		ASSERT_TRUE(request) << "Can't create request";

		ASSERT_EQ(request->addBuffer(stream, buffer.get()), 0) << "Can't set buffer for request";

		requests_.push_back(std::move(request));
	}

	camera_->requestCompleted.connect(this, &Capture::requestComplete);

	ASSERT_EQ(camera_->start(), 0) << "Failed to start camera";
}

void Capture::stop()
{
	if (!config_ || !allocator_.allocated())
		return;

	camera_->stop();

	camera_->requestCompleted.disconnect(this);

	Stream *stream = config_->at(0).stream();
	requests_.clear();
	allocator_.free(stream);
}