/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2020, Google Inc.
 *
 * delayed_controls.cpp - libcamera delayed controls test
 */

#include <iostream>

#include "libcamera/internal/delayed_controls.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/v4l2_videodevice.h"

#include "test.h"

using namespace std;
using namespace libcamera;

class DelayedControlsTest : public Test
{
public:
	DelayedControlsTest()
	{
	}

protected:
	int init() override
	{
		enumerator_ = DeviceEnumerator::create();
		if (!enumerator_) {
			cerr << "Failed to create device enumerator" << endl;
			return TestFail;
		}

		if (enumerator_->enumerate()) {
			cerr << "Failed to enumerate media devices" << endl;
			return TestFail;
		}

		DeviceMatch dm("vivid");
		dm.add("vivid-000-vid-cap");

		media_ = enumerator_->search(dm);
		if (!media_) {
			cerr << "vivid video device found" << endl;
			return TestSkip;
		}

		dev_ = V4L2VideoDevice::fromEntityName(media_.get(), "vivid-000-vid-cap");
		if (dev_->open()) {
			cerr << "Failed to open video device" << endl;
			return TestFail;
		}

		const ControlInfoMap &infoMap = dev_->controls();

		/* Make sure the controls we require are present. */
		if (infoMap.empty()) {
			cerr << "Failed to enumerate controls" << endl;
			return TestFail;
		}

		if (infoMap.find(V4L2_CID_BRIGHTNESS) == infoMap.end() ||
		    infoMap.find(V4L2_CID_CONTRAST) == infoMap.end()) {
			cerr << "Missing controls" << endl;
			return TestFail;
		}

		return TestPass;
	}

	int singleControlNoDelay()
	{
		std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
			{ V4L2_CID_BRIGHTNESS, { 0, false } },
		};
		std::unique_ptr<DelayedControls> delayed =
			std::make_unique<DelayedControls>(dev_.get(), delays);
		ControlList ctrls;

		/* Reset control to value not used in test. */
		ctrls.set(V4L2_CID_BRIGHTNESS, 1);
		dev_->setControls(&ctrls);
		delayed->reset();

		/* Trigger the first frame start event */
		delayed->applyControls(0);

		/* Test control without delay are set at once. */
		for (unsigned int i = 1; i < 100; i++) {
			int32_t value = 100 + i;

			ctrls.set(V4L2_CID_BRIGHTNESS, value);
			delayed->push(ctrls);

			delayed->applyControls(i);

			ControlList result = delayed->get(i);
			int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
			if (brightness != value) {
				cerr << "Failed single control without delay"
				     << " frame " << i
				     << " expected " << value
				     << " got " << brightness
				     << endl;
				return TestFail;
			}
		}

		return TestPass;
	}

	int singleControlWithDelay()
	{
		std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
			{ V4L2_CID_BRIGHTNESS, { 1, false } },
		};
		std::unique_ptr<DelayedControls> delayed =
			std::make_unique<DelayedControls>(dev_.get(), delays);
		ControlList ctrls;

		/* Reset control to value that will be first in test. */
		int32_t expected = 4;
		ctrls.set(V4L2_CID_BRIGHTNESS, expected);
		dev_->setControls(&ctrls);
		delayed->reset();

		/* Trigger the first frame start event */
		delayed->applyControls(0);

		/* Test single control with delay. */
		for (unsigned int i = 1; i < 100; i++) {
			int32_t value = 10 + i;

			ctrls.set(V4L2_CID_BRIGHTNESS, value);
			delayed->push(ctrls);

			delayed->applyControls(i);

			ControlList result = delayed->get(i);
			int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
			if (brightness != expected) {
				cerr << "Failed single control with delay"
				     << " frame " << i
				     << " expected " << expected
				     << " got " << brightness
				     << endl;
				return TestFail;
			}

			expected = value;
		}

		return TestPass;
	}

	int dualControlsWithDelay(uint32_t startOffset)
	{
		static const unsigned int maxDelay = 2;

		std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
			{ V4L2_CID_BRIGHTNESS, { 1, false } },
			{ V4L2_CID_CONTRAST, { maxDelay, false } },
		};
		std::unique_ptr<DelayedControls> delayed =
			std::make_unique<DelayedControls>(dev_.get(), delays);
		ControlList ctrls;

		/* Reset control to value that will be first two frames in test. */
		int32_t expected = 200;
		ctrls.set(V4L2_CID_BRIGHTNESS, expected);
		ctrls.set(V4L2_CID_CONTRAST, expected + 1);
		dev_->setControls(&ctrls);
		delayed->reset();

		/* Trigger the first frame start event */
		delayed->applyControls(startOffset);

		/* Test dual control with delay. */
		for (unsigned int i = 1; i < 100; i++) {
			uint32_t frame = startOffset + i;
			int32_t value = 10 + i;

			ctrls.set(V4L2_CID_BRIGHTNESS, value);
			ctrls.set(V4L2_CID_CONTRAST, value + 1);
			delayed->push(ctrls);

			delayed->applyControls(frame);

			ControlList result = delayed->get(frame);
			int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
			int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
			if (brightness != expected || contrast != expected + 1) {
				cerr << "Failed dual controls"
				     << " frame " << frame
				     << " brightness " << brightness
				     << " contrast " << contrast
				     << " expected " << expected
				     << endl;
				return TestFail;
			}

			expected = i < maxDelay ? expected : value - 1;
		}

		return TestPass;
	}

	int dualControlsMultiQueue()
	{
		static const unsigned int maxDelay = 2;

		std::unordered_map<uint32_t, DelayedControls::ControlParams> delays = {
			{ V4L2_CID_BRIGHTNESS, { 1, false } },
			{ V4L2_CID_CONTRAST, { maxDelay, false } }
		};
		std::unique_ptr<DelayedControls> delayed =
			std::make_unique<DelayedControls>(dev_.get(), delays);
		ControlList ctrls;

		/* Reset control to value that will be first two frames in test. */
		int32_t expected = 100;
		ctrls.set(V4L2_CID_BRIGHTNESS, expected);
		ctrls.set(V4L2_CID_CONTRAST, expected);
		dev_->setControls(&ctrls);
		delayed->reset();

		/* Trigger the first frame start event */
		delayed->applyControls(0);

		/*
		 * Queue all controls before any fake frame start. Note we
		 * can't queue up more then the delayed controls history size
		 * which is 16. Where one spot is used by the reset control.
		 */
		for (unsigned int i = 0; i < 15; i++) {
			int32_t value = 10 + i;

			ctrls.set(V4L2_CID_BRIGHTNESS, value);
			ctrls.set(V4L2_CID_CONTRAST, value);
			delayed->push(ctrls);
		}

		/* Process all queued controls. */
		for (unsigned int i = 1; i < 16; i++) {
			int32_t value = 10 + i - 1;

			delayed->applyControls(i);

			ControlList result = delayed->get(i);

			int32_t brightness = result.get(V4L2_CID_BRIGHTNESS).get<int32_t>();
			int32_t contrast = result.get(V4L2_CID_CONTRAST).get<int32_t>();
			if (brightness != expected || contrast != expected) {
				cerr << "Failed multi queue"
				     << " frame " << i
				     << " brightness " << brightness
				     << " contrast " << contrast
				     << " expected " << expected
				     << endl;
				return TestFail;
			}

			expected = i < maxDelay ? expected : value - 1;
		}

		return TestPass;
	}

	int run() override
	{
		int ret;

		/* Test single control without delay. */
		ret = singleControlNoDelay();
		if (ret)
			return ret;

		/* Test single control with delay. */
		ret = singleControlWithDelay();
		if (ret)
			return ret;

		/* Test dual controls with different delays. */
		ret = dualControlsWithDelay(0);
		if (ret)
			return ret;

		/* Test dual controls with non-zero sequence start. */
		ret = dualControlsWithDelay(10000);
		if (ret)
			return ret;

		/* Test dual controls with sequence number wraparound. */
		ret = dualControlsWithDelay(UINT32_MAX - 50);
		if (ret)
			return ret;

		/* Test control values produced faster than consumed. */
		ret = dualControlsMultiQueue();
		if (ret)
			return ret;

		return TestPass;
	}

private:
	std::unique_ptr<DeviceEnumerator> enumerator_;
	std::shared_ptr<MediaDevice> media_;
	std::unique_ptr<V4L2VideoDevice> dev_;
};

TEST_REGISTER(DelayedControlsTest)