#include "hardware/drivers/serial/SerialQtDriver.h"

#include <QByteArray>
#include <QMetaObject>
#include <QThread>

#include "utils/logs/logging.h"

namespace softbus::hardware::drivers::serial
{

SerialQtDriver::SerialQtDriver(QObject* parent)
    : QObject(parent),
      m_port(this)
{
    connect(&m_port, &QSerialPort::readyRead, this, &SerialQtDriver::handleReadyRead);
    connect(&m_port, &QSerialPort::errorOccurred, this, &SerialQtDriver::handleError);
}

SerialQtDriver::~SerialQtDriver()
{
    Close();
}

bool SerialQtDriver::Open(const std::string& endpoint)
{
    const QString ep = QString::fromStdString(endpoint);
    if (QThread::currentThread() == this->thread()) {
        return openOnOwnerThread(ep);
    }

    bool ok = false;
    QMetaObject::invokeMethod(
        this,
        [this, ep, &ok]() { ok = openOnOwnerThread(ep); },
        Qt::BlockingQueuedConnection);
    return ok;
}

void SerialQtDriver::Close()
{
    if (QThread::currentThread() == this->thread()) {
        closeOnOwnerThread();
        return;
    }
    QMetaObject::invokeMethod(this, [this]() { closeOnOwnerThread(); }, Qt::BlockingQueuedConnection);
}

bool SerialQtDriver::Write(const std::vector<std::uint8_t>& data)
{
    return Write(data.data(), data.size());
}

bool SerialQtDriver::Write(const std::uint8_t* data, std::size_t size)
{
    if (!data || size == 0) {
        return true;
    }

    const QByteArray bytes(reinterpret_cast<const char*>(data), static_cast<int>(size));
    // QSerialPort 必须在其所属线程内访问；若从外部线程调用，切回 owner 线程执行。
    if (QThread::currentThread() == this->thread()) {
        return writeOnOwnerThread(bytes);
    }

    bool ok = false;
    QMetaObject::invokeMethod(
        this,
        [this, bytes, &ok]() { ok = writeOnOwnerThread(bytes); },
        Qt::BlockingQueuedConnection);
    return ok;
}

void SerialQtDriver::SetReadCallback(ReadCallback cb)
{
    m_readCallback = std::move(cb);
}

void SerialQtDriver::SetErrorCallback(ErrorCallback cb)
{
    m_errorCallback = std::move(cb);
}

void SerialQtDriver::SetBaudRate(QSerialPort::BaudRate baud)
{
    m_port.setBaudRate(baud);
}

void SerialQtDriver::SetDataBits(QSerialPort::DataBits bits)
{
    m_port.setDataBits(bits);
}

void SerialQtDriver::SetParity(QSerialPort::Parity parity)
{
    m_port.setParity(parity);
}

void SerialQtDriver::SetStopBits(QSerialPort::StopBits stopBits)
{
    m_port.setStopBits(stopBits);
}

void SerialQtDriver::SetFlowControl(QSerialPort::FlowControl flow)
{
    m_port.setFlowControl(flow);
}

void SerialQtDriver::handleReadyRead()
{
    if (!m_readCallback) {
        const QByteArray data = m_port.readAll();
        LOG_DEBUG() << "SerialQtDriver: dropped" << data.size() << "bytes on" << m_port.portName()
                    << "because no read callback is set";
        return;
    }

    const QByteArray data = m_port.readAll();
    if (data.isEmpty()) {
        return;
    }

    std::vector<std::uint8_t> buffer(reinterpret_cast<const std::uint8_t*>(data.constData()),
                                     reinterpret_cast<const std::uint8_t*>(data.constData()) + data.size());
    m_readCallback(buffer);
}

void SerialQtDriver::handleError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::NoError) {
        return;
    }

    const QString msg =
        QStringLiteral("SerialQtDriver error on %1: %2").arg(m_port.portName(), m_port.errorString());

    LOG_WARNING() << msg;

    if (m_errorCallback) {
        m_errorCallback(msg.toStdString());
    }
}

bool SerialQtDriver::writeOnOwnerThread(const QByteArray& bytes)
{
    if (!m_port.isOpen()) {
        return false;
    }
    const qint64 written = m_port.write(bytes);
    if (written != bytes.size()) {
        LOG_WARNING() << "SerialQtDriver: partial write on" << m_port.portName() << "written" << written << "of"
                      << bytes.size();
        return false;
    }
    return true;
}

bool SerialQtDriver::openOnOwnerThread(const QString& endpoint)
{
    if (m_port.isOpen()) {
        return true;
    }

    m_port.setPortName(endpoint);
    if (!m_port.open(QIODevice::ReadWrite)) {
        if (m_errorCallback) {
            m_errorCallback(m_port.errorString().toStdString());
        }
        LOG_WARNING() << "SerialQtDriver: failed to open port" << m_port.portName() << ":" << m_port.errorString();
        return false;
    }

    LOG_DEBUG() << "SerialQtDriver: opened port" << m_port.portName();
    return true;
}

void SerialQtDriver::closeOnOwnerThread()
{
    if (m_port.isOpen()) {
        const QString name = m_port.portName();
        m_port.close();
        LOG_DEBUG() << "SerialQtDriver: closed port" << name;
    }
}

} // namespace softbus::hardware::drivers::serial

