#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <myapp/modbus/connection.h>
#include <myapp/modbus/poll.h>
#include "logging/nulllogger.h"
#include <atomic>
#include <chrono>
#include <thread>

using namespace ::testing;
using namespace myapp::modbus;

class ModbusPollTests : public Test
{
public:
    std::shared_ptr<poll> poll_ = poll::create();
};

TEST_F(ModbusPollTests, TaskInQueue_IsCalled)
{
    bool is_called = false;
    auto task = [&is_called] { is_called = true; };

    auto executor = poll_->executor();
    executor(std::move(task));

    poll_->run();

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    ASSERT_TRUE(is_called);
}

TEST_F(ModbusPollTests, TaskAddingTask_BothAreCalled)
{
    bool is_called = false;
    auto task = [&is_called] { is_called = true; };

    auto executor = poll_->executor();
    executor([add_task = executor, task = std::move(task)] {
        add_task(task);
    });

    poll_->run();

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    ASSERT_TRUE(is_called);
}

TEST_F(ModbusPollTests, TaskAddedAfterLastTask_IsNotCalled)
{
    bool is_called = false;
    auto task = [&is_called] { is_called = true; };
    auto dummy_task = [] { };

    auto executor = poll_->executor();
    executor(std::move(dummy_task));

    poll_->run();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    executor(std::move(task));
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    ASSERT_FALSE(is_called);
}

TEST_F(ModbusPollTests, OnAbort_NotAllTasksAreCalled)
{
    constexpr int max_tasks = 20;
    std::atomic<int> called_times = ATOMIC_VAR_INIT(0);
    auto executor = poll_->executor();

    poll::task_t task;
    task = [&task, executor, &called_times]
    {
        if (++called_times < max_tasks)
            executor(task);

        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    };
    executor(task);

    poll_->run();
    std::this_thread::sleep_for(std::chrono::milliseconds(50));

    poll_->abort();
    std::this_thread::sleep_for(std::chrono::milliseconds(200));

    ASSERT_LE(called_times, 10);
}

class ModbusConnectionTests : public Test
{
public:
    std::shared_ptr<null_logger> loggerMock_ = std::make_shared<null_logger>();
};

TEST_F(ModbusConnectionTests, CreateRtuWithColonDelimiter_DoesNotThrow)
{
    ASSERT_NO_THROW({ connection::create(loggerMock_, u8"rtu:/dev/ttyS0:9600:8:N:1"); });
}

TEST_F(ModbusConnectionTests, CreateTcpWithColonDelimiter_DoesNotThrow)
{
    ASSERT_NO_THROW({ connection::create(loggerMock_, u8"tcp:localhost:502"); });
}

TEST_F(ModbusConnectionTests, CreateTcp6WithColonDelimiter_DoesNotThrow)
{
	ASSERT_NO_THROW({ connection::create(loggerMock_, u8"tcp:[::1]:502"); });
}

TEST_F(ModbusConnectionTests, CreateEmptyOrInvalidConnectionString_Throws)
{
    EXPECT_THROW({ connection::create(loggerMock_, u8""); }, std::runtime_error);
    EXPECT_THROW({ connection::create(loggerMock_, u8":::"); }, std::runtime_error);
    EXPECT_THROW({ connection::create(loggerMock_, u8"invalid"); }, std::runtime_error);
    EXPECT_THROW({ connection::create(loggerMock_, u8"invalid:::"); }, std::runtime_error);
    EXPECT_THROW({ connection::create(loggerMock_, u8"invalid:::"); }, std::runtime_error);

    EXPECT_THROW({ connection::create(loggerMock_, u8"rtu:::::"); }, std::runtime_error);
    EXPECT_THROW({ connection::create(loggerMock_, u8"tcp::"); }, std::runtime_error);
}
