/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * This file is part of libgpiod.
 *
 * Copyright (C) 2019 Bartosz Golaszewski <bgolaszewski@baylibre.com>
 */

#include <catch2/catch.hpp>
#include <gpiod.hpp>
#include <poll.h>
#include <thread>

#include "gpio-mockup.hpp"

using ::gpiod::test::mockup;

namespace {

const ::std::string consumer = "event-test";

} /* namespace */

TEST_CASE("Line events can be detected", "[event][line]")
{
	mockup::probe_guard mockup_chips({ 8 });
	mockup::event_thread events(0, 4, 200);
	::gpiod::chip chip(mockup::instance().chip_name(0));
	auto line = chip.get_line(4);
	::gpiod::line_request config;

	config.consumer = consumer.c_str();

	SECTION("rising edge")
	{
		config.request_type = ::gpiod::line_request::EVENT_RISING_EDGE;
		line.request(config);

		auto got_event = line.event_wait(::std::chrono::seconds(1));
		REQUIRE(got_event);

		auto event = line.event_read();
		REQUIRE(event.source == line);
		REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
	}

	SECTION("falling edge")
	{
		config.request_type = ::gpiod::line_request::EVENT_FALLING_EDGE;

		line.request(config);

		auto got_event = line.event_wait(::std::chrono::seconds(1));
		REQUIRE(got_event);

		auto event = line.event_read();
		REQUIRE(event.source == line);
		REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
	}

	SECTION("both edges")
	{
		config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;

		line.request(config);

		auto got_event = line.event_wait(::std::chrono::seconds(1));
		REQUIRE(got_event);

		auto event = line.event_read();
		REQUIRE(event.source == line);
		REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);

		got_event = line.event_wait(::std::chrono::seconds(1));
		REQUIRE(got_event);

		event = line.event_read();
		REQUIRE(event.source == line);
		REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
	}

	SECTION("active-low")
	{
		config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
		config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;

		line.request(config);

		auto got_event = line.event_wait(::std::chrono::seconds(1));
		REQUIRE(got_event);

		auto event = line.event_read();
		REQUIRE(event.source == line);
		REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);

		got_event = line.event_wait(::std::chrono::seconds(1));
		REQUIRE(got_event);

		event = line.event_read();
		REQUIRE(event.source == line);
		REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
	}
}

TEST_CASE("Watching line_bulk objects for events works", "[event][bulk]")
{
	mockup::probe_guard mockup_chips({ 8 });
	mockup::event_thread events(0, 2, 200);
	::gpiod::chip chip(mockup::instance().chip_name(0));
	auto lines = chip.get_lines({ 0, 1, 2, 3 });
	::gpiod::line_request config;

	config.consumer = consumer.c_str();
	config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
	lines.request(config);

	auto event_lines = lines.event_wait(::std::chrono::seconds(1));
	REQUIRE(event_lines);
	REQUIRE(event_lines.size() == 1);

	auto event = event_lines.get(0).event_read();
	REQUIRE(event.source == event_lines.get(0));
	REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);

	event_lines = lines.event_wait(::std::chrono::seconds(1));
	REQUIRE(event_lines);
	REQUIRE(event_lines.size() == 1);

	event = event_lines.get(0).event_read();
	REQUIRE(event.source == event_lines.get(0));
	REQUIRE(event.event_type == ::gpiod::line_event::FALLING_EDGE);
}

TEST_CASE("It's possible to retrieve the event file descriptor", "[event][line]")
{
	mockup::probe_guard mockup_chips({ 8 });
	::gpiod::chip chip(mockup::instance().chip_name(0));
	auto line = chip.get_line(4);
	::gpiod::line_request config;

	config.consumer = consumer.c_str();

	SECTION("get the fd")
	{
		config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;

		line.request(config);
		REQUIRE(line.event_get_fd() >= 0);
	}

	SECTION("error if not requested")
	{
		REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error);
	}

	SECTION("error if requested for values")
	{
		config.request_type = ::gpiod::line_request::DIRECTION_INPUT;

		line.request(config);
		REQUIRE_THROWS_AS(line.event_get_fd(), ::std::system_error);
	}
}

TEST_CASE("Event file descriptors can be used for polling", "[event]")
{
	mockup::probe_guard mockup_chips({ 8 });
	mockup::event_thread events(0, 3, 200);
	::gpiod::chip chip(mockup::instance().chip_name(0));
	auto lines = chip.get_lines({ 0, 1, 2, 3, 4, 5 });

	::gpiod::line_request config;
	config.consumer = consumer.c_str();
	config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;

	lines.request(config);

	::std::vector<::pollfd> fds(3);
	fds[0].fd = lines[1].event_get_fd();
	fds[1].fd = lines[3].event_get_fd();
	fds[2].fd = lines[5].event_get_fd();

	fds[0].events = fds[1].events = fds[2].events = POLLIN | POLLPRI;

	int ret = ::poll(fds.data(), 3, 1000);
	REQUIRE(ret == 1);

	for (int i = 0; i < 3; i++) {
		if (fds[i].revents) {
			auto event = lines[3].event_read();
			REQUIRE(event.source == lines[3]);
			REQUIRE(event.event_type == ::gpiod::line_event::RISING_EDGE);
		}
	}
}

TEST_CASE("It's possible to read a value from a line requested for events", "[event][line]")
{
	mockup::probe_guard mockup_chips({ 8 });
	::gpiod::chip chip(mockup::instance().chip_name(0));
	auto line = chip.get_line(4);
	::gpiod::line_request config;

	config.consumer = consumer.c_str();
	config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;

	mockup::instance().chip_set_pull(0, 4, 1);

	SECTION("active-high (default)")
	{
		line.request(config);
		REQUIRE(line.get_value() == 1);
	}

	SECTION("active-low")
	{
		config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
		line.request(config);
		REQUIRE(line.get_value() == 0);
	}
}

TEST_CASE("It's possible to read values from lines requested for events", "[event][bulk]")
{
	mockup::probe_guard mockup_chips({ 8 });
	::gpiod::chip chip(mockup::instance().chip_name(0));
	auto lines = chip.get_lines({ 0, 1, 2, 3, 4 });
	::gpiod::line_request config;

	config.consumer = consumer.c_str();
	config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;

	mockup::instance().chip_set_pull(0, 5, 1);

	SECTION("active-high (default)")
	{
		lines.request(config);
		REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 0, 0, 0, 0 }));
		mockup::instance().chip_set_pull(0, 1, 1);
		mockup::instance().chip_set_pull(0, 3, 1);
		mockup::instance().chip_set_pull(0, 4, 1);
		REQUIRE(lines.get_values() == ::std::vector<int>({ 0, 1, 0, 1, 1 }));
	}

	SECTION("active-low")
	{
		config.flags = ::gpiod::line_request::FLAG_ACTIVE_LOW;
		lines.request(config);
		REQUIRE(lines.get_values() == ::std::vector<int>({ 1, 1, 1, 1, 1 }));
		mockup::instance().chip_set_pull(0, 1, 1);
		mockup::instance().chip_set_pull(0, 3, 1);
		mockup::instance().chip_set_pull(0, 4, 1);
		REQUIRE(lines.get_values() == ::std::vector<int>({ 1, 0, 1, 0, 0 }));
	}
}

TEST_CASE("It's possible to read more than one line event", "[event][line]")
{
	mockup::probe_guard mockup_chips({ 8 });
	::gpiod::chip chip(mockup::instance().chip_name(0));
	auto line = chip.get_line(4);
	::gpiod::line_request config;

	config.consumer = consumer.c_str();
	config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;

	line.request(config);

	mockup::instance().chip_set_pull(0, 4, 1);
	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
	mockup::instance().chip_set_pull(0, 4, 0);
	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
	mockup::instance().chip_set_pull(0, 4, 1);
	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));

	auto events = line.event_read_multiple();

	REQUIRE(events.size() == 3);
	REQUIRE(events.at(0).event_type == ::gpiod::line_event::RISING_EDGE);
	REQUIRE(events.at(1).event_type == ::gpiod::line_event::FALLING_EDGE);
	REQUIRE(events.at(2).event_type == ::gpiod::line_event::RISING_EDGE);
	REQUIRE(events.at(0).source == line);
	REQUIRE(events.at(1).source == line);
	REQUIRE(events.at(2).source == line);
}
