#include <myapp/modbus/devicefactory.h>
#include "devices/MEg40.h"
#include "devices/relay485.h"
#include "utils/connectionstringhelper.h"
#include <chrono>
#include <functional>
#include <map>

using namespace std::string_literals;

namespace myapp::modbus {

namespace {

void prepend_device_name_if_exception_thrown(std::string const& device_name, std::function<void()> && f)
{
    try
    {
        f();
    }
    catch (std::runtime_error const& e)
    {
        throw std::runtime_error(device_name + ": "s + e.what());
    }
    catch (std::invalid_argument const& e)
    {
        throw std::runtime_error(device_name + ": "s + e.what());
    }
    catch (std::logic_error const& e)
    {
        throw std::runtime_error(device_name + ": "s + e.what());
    }
    catch (std::exception const& e)
    {
        throw std::runtime_error(device_name + ": "s + e.what());
    }
}

}

std::shared_ptr<device> device_factory::create(
    std::shared_ptr<connection> conn,
    std::string name,
    std::string const& connection_string,
    std::shared_ptr<logging::logger> logger)
{
    auto params = utils::split_parameters(connection_string);

    std::string device_type;
    int slave_id;
    auto period = std::chrono::seconds::rep{ -1 };

    prepend_device_name_if_exception_thrown(name, [&]
    {
        if (params[0].find_first_of(utils::value_separator) != std::string::npos)
            throw std::runtime_error("Invalid modbus device type in connection string");

        device_type = std::move(params[0]);
        slave_id = std::stoi(utils::extract_parameter(params, "slave-id"s + utils::value_separator, false));

        utils::extract_parameter_and_invoke_if_present(params, "period"s + utils::value_separator, [&period](std::string value)
        {
            period = std::stoi(value);
        });
    });

    std::map<std::string, std::function<std::shared_ptr<device>()>> builder;
    builder["relay485"s] = [&]
    {
        return std::make_shared<devices::relay485>(std::move(name), slave_id, std::move(conn), std::move(logger));
    };

    builder["MEg40"s] = [&]
    {
        auto instance = std::make_shared<devices::MEg40>(std::move(name), slave_id, std::move(conn), std::move(logger));
        if (period != std::chrono::seconds::rep{ -1 })
            instance->set_read_period(period);

        return instance;
    };

    try 
    {
        return builder[device_type]();
    }
    catch (std::bad_function_call const&)
    {
        throw std::runtime_error("Unknown device type '" + device_type + "'");
    }
}

} /* namespace myapp::modbus */
