#include "serial.h"

#include <asio.hpp>
namespace cppbox {

using serial_port = asio::serial_port;
class CSerialOprImp
{
public:
    CSerialOprImp()
        : timer_(io_),
          baud_rate_(serial_port::baud_rate(19200)),
          stop_bits_(serial_port::stop_bits::one),
          parity_(serial_port::parity::none),
          flow_control_(serial_port::flow_control::none)
    {
        port_ = new serial_port(io_);
        error_ = new char[1024];
        std::memset(error_, 0x0, 1024);
    }
    ~CSerialOprImp()
    {
        close();
        delete port_;
        delete[] error_;
    }

public:
    void set_port(const char* port)
    {
        std::memset(port_name_, 0x0, sizeof(port_name_));
        std::snprintf(port_name_, sizeof(port_name_), "%s", port);
    }
    int open()
    {
        port_->open(port_name_, ec_);
        if (!port_->is_open()) {
            std::memset(error_, 0x0, 1024);
            std::snprintf(error_, 1024, "%s", ec_.message().c_str());
            return ec_.value();
        }
        port_->set_option(baud_rate_, ec_);
        port_->set_option(flow_control_, ec_);
        port_->set_option(parity_, ec_);
        port_->set_option(stop_bits_, ec_);
        port_->set_option(character_size_, ec_);
        return 0;
    }
    void close()
    {
        if (port_->is_open()) {
            port_->close();
        }
    }

    int write(const char* data, int len)
    {
        bytes_transferred_ = 0;
        io_.reset();

        asio::error_code write_ec;

        std::size_t ret = asio::write(*port_, asio::buffer(data, len), write_ec);
        int         send_size = static_cast<int>(ret);
        return send_size;
    }

    int read(char* data)
    {
        bytes_transferred_ = 0;
        io_.reset();

        timer_.expires_from_now(asio::chrono::milliseconds(time_out_));
        timer_.async_wait(std::bind(&CSerialOprImp::time_out, this, asio::placeholders::error));

        port_->async_read_some(
            asio::buffer(buffer_),
            std::bind(&CSerialOprImp::handle_read, this, asio::placeholders::error,
                      asio::placeholders::bytes_transferred));

        io_.run();
        if (bytes_transferred_ > 0) {
            std::memcpy(data, buffer_, bytes_transferred_);
            data[bytes_transferred_] = '\0';
        }
        return bytes_transferred_;
    }
    const char* get_last_error() const { return error_; }
    void        set_timeout(uint64_t timeout) { time_out_ = timeout; }

    void set_baudrate(uint32_t baudrate) { baud_rate_ = serial_port::baud_rate(baudrate); }
    void set_databits(serial_port::character_size size) { character_size_ = size; }
    void set_parity(serial_port::parity parity) { parity_ = parity; }
    void set_stopbits(serial_port::stop_bits stop_bits) { stop_bits_ = stop_bits; }
    void set_flow_control(serial_port::flow_control flow_control) { flow_control_ = flow_control; }

private:
    void time_out(const asio::error_code& code)
    {
        if (code) {
            return;
        }
        bytes_transferred_ = -1;
        port_->cancel();
    }
    void handle_read(const asio::error_code& error, std::size_t bytes_transferred)
    {
        if (!error) {
            bytes_transferred_ = static_cast<int>(bytes_transferred);
            timer_.cancel();
        }
    }

private:
    char                        port_name_[128]{};
    serial_port::baud_rate      baud_rate_;
    serial_port::character_size character_size_{8};
    serial_port::stop_bits      stop_bits_;
    serial_port::parity         parity_;
    uint64_t                    time_out_{1000};
    serial_port::flow_control   flow_control_;

private:
    int                bytes_transferred_{};
    asio::io_context   io_{};
    asio::serial_port* port_{};
    asio::error_code   ec_{};
    char               buffer_[512]{};
    asio::steady_timer timer_;
    char*              error_{};
};

CSerialOpr::CSerialOpr()
{
    imp_ = new CSerialOprImp();
}

CSerialOpr::~CSerialOpr()
{
    delete imp_;
}

void CSerialOpr::set_port(const char* port)
{
    imp_->set_port(port);
}

void CSerialOpr::set_baudrate(uint32_t baudrate)
{
    imp_->set_baudrate(baudrate);
}

void CSerialOpr::set_data_bits(DataBits data_bits)
{
    switch (data_bits) {
        case D5:
            imp_->set_databits(serial_port::character_size(5));
            break;
        case D6:
            imp_->set_databits(serial_port::character_size(6));
            break;
        case D7:
            imp_->set_databits(serial_port::character_size(7));
            break;
        case D8:
            imp_->set_databits(serial_port::character_size(8));
            break;
        default:
            imp_->set_databits(serial_port::character_size(5));
            break;
    }
}

void CSerialOpr::set_stop_bits(StopBits stop_bits)
{
    switch (stop_bits) {
        case OneStop:
            imp_->set_stopbits(serial_port::stop_bits(serial_port::stop_bits::one));
            break;
        case OneAndHalfStop:
            imp_->set_stopbits(serial_port::stop_bits(serial_port::stop_bits::onepointfive));
            break;
        case TwoStop:
            imp_->set_stopbits(serial_port::stop_bits(serial_port::stop_bits::two));
            break;
        default:
            imp_->set_stopbits(serial_port::stop_bits(serial_port::stop_bits::one));
            break;
    }
}

void CSerialOpr::set_parity(Parity parity)
{
    switch (parity) {
        case EvenParity:
            imp_->set_parity(serial_port::parity(serial_port::parity::even));
            break;
        case OddParity:
            imp_->set_parity(serial_port::parity(serial_port::parity::odd));
            break;
        default:
            imp_->set_parity(serial_port::parity(serial_port::parity::none));
            break;
    }
}

void CSerialOpr::set_flow_control(FlowControl flow_control)
{
    switch (flow_control) {
        case NoFlowControl:
            imp_->set_flow_control(serial_port::flow_control(serial_port::flow_control::none));
            break;
        case HardwareControl:
            imp_->set_flow_control(serial_port::flow_control(serial_port::flow_control::hardware));
            break;
        case SoftwareControl:
            imp_->set_flow_control(serial_port::flow_control(serial_port::flow_control::software));
            break;
        default:
            imp_->set_flow_control(serial_port::flow_control(serial_port::flow_control::none));
            break;
    }
}

void CSerialOpr::set_timeout(uint64_t timeout)
{
    imp_->set_timeout(timeout);
}

int CSerialOpr::open()
{
    return imp_->open();
}

void CSerialOpr::close()
{
    imp_->close();
}
int CSerialOpr::read(char* data)
{
    return imp_->read(data);
}
int CSerialOpr::write(const char* data, int len)
{
    return imp_->write(data, len);
}
const char* CSerialOpr::get_last_error() const
{
    return imp_->get_last_error();
}
}   // namespace cppbox