// clang-format off
#include <STM32UART.hpp>
#include <STM32DMA.hpp>
#include <STM32Timer.hpp>
#include <cstring>
#include <string_view>
#include <system_time.hpp>
#include <TimerManager.hpp>
#include <CppUTest/CommandLineTestRunner.h>
// clang-format on

#define LF_CHAR (0x0A) // LF char in hex

const char transmit_buffer[] = "Hello, there!\r\n";
const std::string_view strViewCmd = "Hello, there!\r\n";
constexpr size_t transmit_len = sizeof(transmit_buffer) - 1;
char receive_buffer[transmit_len + 1];
bool receive_callback_called;

TEST_GROUP(UartLoopbackTest){};

TEST(UartLoopbackTest, LoopbackTestForEquality)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;

	usart1.start();
	usart1.receive(reinterpret_cast<uint8_t*>(receive_buffer), transmit_len,
				   [&](UART::status result) {
					   CHECK(result == UART::status::OK);
					   STRNCMP_EQUAL(reinterpret_cast<char*>(receive_buffer),
									 reinterpret_cast<const char*>(transmit_buffer), transmit_len);
					   receive_callback_called = true;
				   });
	usart1.transmit(reinterpret_cast<const uint8_t*>(transmit_buffer), transmit_len);

	systime::delay(250);

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, LoopbackTestForEqualityStrView)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;

	usart1.start();
	usart1.receive(reinterpret_cast<uint8_t*>(receive_buffer), transmit_len,
				   [&](UART::status result) {
					   CHECK(result == UART::status::OK);
					   STRNCMP_EQUAL(reinterpret_cast<char*>(receive_buffer), strViewCmd.data(),
									 strViewCmd.size());
					   receive_callback_called = true;
				   });
	usart1.transmit(strViewCmd);

	systime::delay(250);

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, LoopbackTestForEqualityWithTimeout)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;

	usart1.start();
	usart1.receive(
		reinterpret_cast<uint8_t*>(receive_buffer), transmit_len,
		[&](UART::status result) {
			CHECK(result == UART::status::OK);
			STRNCMP_EQUAL(reinterpret_cast<char*>(receive_buffer),
						  reinterpret_cast<const char*>(transmit_buffer), transmit_len);
			receive_callback_called = true;
		},
		std::chrono::milliseconds(100));
	usart1.transmit(reinterpret_cast<const uint8_t*>(transmit_buffer), transmit_len);

	systime::delay(250);

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, LoopbackTestWithLongerRxCount)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;

	usart1.start();
	usart1.receive(reinterpret_cast<uint8_t*>(receive_buffer), transmit_len * 2,
				   [&](UART::status result) {
					   CHECK(result == UART::status::OK);
					   STRNCMP_EQUAL(reinterpret_cast<char*>(receive_buffer),
									 reinterpret_cast<const char*>(transmit_buffer), transmit_len);
					   receive_callback_called = true;
				   });
	usart1.transmit(reinterpret_cast<const uint8_t*>(transmit_buffer), transmit_len);

	systime::delay(250);

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, LoopbackTestWithShorterRxCount)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;
	constexpr size_t shortened_size = transmit_len / 2;

	usart1.start();
	usart1.receive(
		reinterpret_cast<uint8_t*>(receive_buffer), shortened_size, [&](UART::status result) {
			CHECK(result == UART::status::OK);
			STRNCMP_EQUAL(reinterpret_cast<char*>(receive_buffer),
						  reinterpret_cast<const char*>(transmit_buffer), shortened_size);
			receive_callback_called = true;
		});
	usart1.transmit(reinterpret_cast<const uint8_t*>(transmit_buffer), transmit_len);

	systime::delay(250);

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, ShortTransmitTimeoutTest)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	const char short_transmit_buffer[] = "Hello!"; // For this test to work, we need NO LF character
	constexpr size_t short_transmit_len = sizeof(short_transmit_buffer) - 1;
	receive_callback_called = false;

	// For this test to work, we need a shorter transmit buffer
	CHECK(transmit_len > short_transmit_len);

	usart1.start();

	// Here we will set up a receive with a timeout, and transmit fewer bytes than we expect.
	// The result is that a timeout should be called.
	usart1.receive(
		reinterpret_cast<uint8_t*>(receive_buffer), transmit_len,
		[&](UART::status result) {
			CHECK(result == UART::status::TIMEOUT);
			receive_callback_called = true;
		},
		std::chrono::milliseconds(100));
	usart1.transmit(reinterpret_cast<const uint8_t*>(short_transmit_buffer), short_transmit_len);

	systime::delay(250);

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, NoTransmitTimeoutTest)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;

	usart1.start();

	// Here we will set up a receive with a timeout, and transmit fewer bytes than we expect.
	// The result is that a timeout should be called.
	usart1.receive(
		reinterpret_cast<uint8_t*>(receive_buffer), transmit_len,
		[&](UART::status result) {
			CHECK(result == UART::status::TIMEOUT);
			receive_callback_called = true;
		},
		std::chrono::milliseconds(100));

	systime::delay(std::chrono::milliseconds(5));

	CHECK_FALSE(receive_callback_called);

	systime::delay(std::chrono::milliseconds(50));

	CHECK_FALSE(receive_callback_called);

	systime::delay(std::chrono::milliseconds(50));

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, NoTransmitTimeoutTestUsingTimerManager)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	TimerManager<0> timer_mgr(timer);
	timer_mgr.start();
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;

	usart1.start();

	// Here we will set up a receive with a timeout, and transmit fewer bytes than we expect.
	// The result is that a timeout should be called.
	usart1.receive(
		reinterpret_cast<uint8_t*>(receive_buffer), transmit_len,
		[&](UART::status result) {
			CHECK(result == UART::status::TIMEOUT);
			receive_callback_called = true;
		},
		std::chrono::milliseconds(100));

	systime::delay(std::chrono::milliseconds(5));

	CHECK_FALSE(receive_callback_called);

	systime::delay(std::chrono::milliseconds(50));

	CHECK_FALSE(receive_callback_called);

	systime::delay(std::chrono::milliseconds(50));

	CHECK(receive_callback_called);
}

TEST(UartLoopbackTest, NoTransmitLongTimeoutTestUsingTimerManager)
{
	STM32DMA usart1_DMA_tx(STM32DMA::device::dma1, STM32DMA::channel::CH0);
	STM32DMA usart1_DMA_rx(STM32DMA::device::dma1, STM32DMA::channel::CH1);
	STM32Timer timer(STM32Timer::Device::CH2);
	TimerManager<0> timer_mgr(timer);
	timer_mgr.start();
	STM32UART::DMA::config uartConfig{.baudrate = 9600,
									  .tx_channel = usart1_DMA_tx,
									  .rx_channel = usart1_DMA_rx,
									  .timeout_timer = timer,
									  .char_match = LF_CHAR};
	STM32UART::DMA usart1(STM32UART::devices::usart1, uartConfig);
	receive_callback_called = false;

	usart1.start();

	// Here we will set up a receive with a timeout, and transmit fewer bytes than we expect.
	// The result is that a timeout should be called.
	usart1.receive(
		reinterpret_cast<uint8_t*>(receive_buffer), transmit_len,
		[&](UART::status result) {
			CHECK(result == UART::status::TIMEOUT);
			receive_callback_called = true;
		},
		std::chrono::seconds(1));

	systime::delay(std::chrono::milliseconds(5));

	CHECK_FALSE(receive_callback_called);

	systime::delay(std::chrono::milliseconds(50));

	CHECK_FALSE(receive_callback_called);

	systime::delay(std::chrono::milliseconds(500));

	CHECK_FALSE(receive_callback_called);

	systime::delay(std::chrono::milliseconds(500));

	CHECK(receive_callback_called);
}
