#include "rawtcpclient.h"
#include "utils/stringcompare.h"
#include <system_error>

using namespace std::string_literals;

namespace myapp::modbus {

static void checked(int result, std::string const& prefix)
{
    if (result == -1)
    {
        if (errno > MODBUS_ENOBASE)
            throw std::runtime_error(prefix + ": " + ::modbus_strerror(errno));

        throw std::system_error(errno, std::system_category(), prefix + ": " + ::modbus_strerror(errno));
    }
}

std::shared_ptr<raw_tcp_client> raw_tcp_client::create(std::shared_ptr<logging::logger> logger, std::string const& connection_string)
{
	try
	{
		if (!utils::starts_with(connection_string, "tcp:"))
			throw std::runtime_error("Not a TCP connection string");

		context ctx(connection_string);
		logger->info("MODBUS TCP", "Created modbus context "s + ctx.description());
		return std::make_shared<raw_tcp_client_impl>(std::move(logger), std::move(ctx));
	}
	catch (std::runtime_error& e)
	{
		logger->error("MODBUS TCP", "Unable to initialize modbus context: "s + e.what());
		throw;
	}
}

raw_tcp_client_impl::raw_tcp_client_impl(std::shared_ptr<logging::logger> logger, context ctx)
  : ctx_(std::move(ctx))
  , logger_(std::move(logger))
{
}

raw_tcp_client_impl::~raw_tcp_client_impl()
{
	modbus_close(ctx_);
}

void raw_tcp_client_impl::connect()
{
	checked(modbus_connect(ctx_), "Connect");
}

std::unique_ptr<raw_data> raw_tcp_client_impl::request(raw_data& adu)
{
	try 
	{
		checked(modbus_send_raw_request(ctx_, adu.data(), adu.data_length()), "tcp send raw request");

		int rc;
		auto response = raw_data::tcp();
		checked(rc = modbus_receive_confirmation(ctx_, &(*response)[0]),
			"tcp receive confirmation");
		if (rc == 0)
			return { nullptr };

		response->size(rc);
		return response;
	}
    catch (std::system_error const& e)
    {
        logger_->info("MODBUS TCP", "Raw request: "s + e.what());
        throw;
    }
	catch (std::runtime_error const& e)
	{
		logger_->info("MODBUS TCP", "Raw request: "s + e.what());
		throw;
	}
}


}