/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * Test the IPA interface
 */

#include <fcntl.h>
#include <iostream>
#include <memory>
#include <string.h>
#include <unistd.h>

#include <libcamera/ipa/vimc_ipa_proxy.h>

#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/object.h>
#include <libcamera/base/shared_fd.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include <libcamera/base/unique_fd.h>

#include "libcamera/internal/camera_manager.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/global_configuration.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/ipa_module.h"
#include "libcamera/internal/pipeline_handler.h"

#include "test.h"

using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;

class IPAInterfaceTest : public Test, public Object
{
public:
	IPAInterfaceTest()
		: trace_(ipa::vimc::IPAOperationNone)
	{
	}

	~IPAInterfaceTest()
	{
		notifier_.reset();
		ipa_.reset();
		ipaManager_.reset();
		cameraManager_.reset();
	}

protected:
	int init() override
	{
		cameraManager_ = make_unique<CameraManager>();

		/* Create a pipeline handler for vimc. */
		const std::vector<PipelineHandlerFactoryBase *> &factories =
			PipelineHandlerFactoryBase::factories();
		for (const PipelineHandlerFactoryBase *factory : factories) {
			if (factory->name() == "vimc") {
				pipe_ = factory->create(cameraManager_.get());
				break;
			}
		}

		if (!pipe_) {
			cerr << "Vimc pipeline not found" << endl;
			return TestPass;
		}

		/* Create the communication pipe. */
		int pipefds[2];
		int ret = pipe2(pipefds, O_NONBLOCK);
		if (ret) {
			ret = errno;
			cerr << "Failed to create IPA test pipe: " << strerror(ret)
			     << endl;
			return TestFail;
		}

		pipeReadFd_ = UniqueFD(pipefds[0]);
		pipeWriteFd_ = SharedFD(pipefds[1]);

		notifier_ = std::make_unique<EventNotifier>(pipeReadFd_.get(),
							    EventNotifier::Read,
							    this);
		notifier_->activated.connect(this, &IPAInterfaceTest::readTrace);

		/* Create the IPA manager. */
		ipaManager_ = std::make_unique<IPAManager>(*cameraManager_);

		return TestPass;
	}

	int run() override
	{
		EventDispatcher *dispatcher = thread()->eventDispatcher();
		Timer timer;

		ipa_ = ipaManager_->createIPA<ipa::vimc::IPAProxyVimc>(pipe_.get(), 0, 0);
		if (!ipa_) {
			cerr << "Failed to create VIMC IPA interface" << endl;
			return TestFail;
		}

		/* Test initialization of IPA module. */
		std::string conf = ipa_->configurationFile("vimc.conf");
		Flags<ipa::vimc::TestFlag> inFlags;
		Flags<ipa::vimc::TestFlag> outFlags;
		int ret = ipa_->init(IPASettings{ conf, "vimc" }, pipeWriteFd_,
				     ipa::vimc::IPAOperationInit,
				     inFlags, &outFlags);
		if (ret < 0) {
			cerr << "IPA interface init() failed" << endl;
			return TestFail;
		}

		timer.start(1000ms);
		while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationInit)
			dispatcher->processEvents();

		if (trace_ != ipa::vimc::IPAOperationInit) {
			cerr << "Failed to test IPA initialization sequence"
			     << endl;
			return TestFail;
		}

		/* Test start of IPA module. */
		ipa_->start();
		timer.start(1000ms);
		while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationStart)
			dispatcher->processEvents();

		if (trace_ != ipa::vimc::IPAOperationStart) {
			cerr << "Failed to test IPA start sequence" << endl;
			return TestFail;
		}

		/* Test stop of IPA module. */
		ipa_->stop();
		timer.start(1000ms);
		while (timer.isRunning() && trace_ != ipa::vimc::IPAOperationStop)
			dispatcher->processEvents();

		if (trace_ != ipa::vimc::IPAOperationStop) {
			cerr << "Failed to test IPA stop sequence" << endl;
			return TestFail;
		}

		return TestPass;
	}

private:
	void readTrace()
	{
		ssize_t s = read(notifier_->fd(), &trace_, sizeof(trace_));
		if (s < 0) {
			int ret = errno;
			cerr << "Failed to read from IPA test pipe: " << strerror(ret)
			     << endl;
			trace_ = ipa::vimc::IPAOperationNone;
		}
	}

	std::shared_ptr<PipelineHandler> pipe_;
	std::unique_ptr<ipa::vimc::IPAProxyVimc> ipa_;
	std::unique_ptr<CameraManager> cameraManager_;
	std::unique_ptr<IPAManager> ipaManager_;
	enum ipa::vimc::IPAOperationCode trace_;
	std::unique_ptr<EventNotifier> notifier_;
	UniqueFD pipeReadFd_;
	SharedFD pipeWriteFd_;
};

TEST_REGISTER(IPAInterfaceTest)
