#include "serial_threads.h"

#include <QDebug>
#include <QtGlobal>

SerialReadThread::SerialReadThread(PacketQueue* packetQueue, QObject* parent)
    : QThread(parent)
    , m_packetQueue(packetQueue) {}

void SerialReadThread::setParseFunc(ParseFunc func) {
    QMutexLocker locker(&m_funcMutex);
    m_parseFunc = std::move(func);
}

void SerialReadThread::enqueueBytes(const QByteArray& data) {
    if (data.isEmpty())
        return;
    QMutexLocker locker(&m_queueMutex);
    m_byteQueue.enqueue(data);
    m_dataReady.wakeOne();
}

void SerialReadThread::clear() {
    QMutexLocker locker(&m_queueMutex);
    m_byteQueue.clear();
    m_buffer.clear();
}

void SerialReadThread::stop() {
    m_running = false;
    m_dataReady.wakeAll();
}

void SerialReadThread::run() {
    m_running = true;
    while (m_running) {
        QByteArray chunk;
        {
            QMutexLocker locker(&m_queueMutex);
            while (m_byteQueue.isEmpty() && m_running)
                m_dataReady.wait(&m_queueMutex);
            if (!m_running)
                break;
            if (!m_byteQueue.isEmpty())
                chunk = m_byteQueue.dequeue();
        }

        if (chunk.isEmpty())
            continue;

        m_buffer.append(chunk);

        ParseFunc parseFunc;
        {
            QMutexLocker locker(&m_funcMutex);
            parseFunc = m_parseFunc;
        }

        if (!parseFunc)
            continue;

        while (m_running) {
            QByteArray packet;
            QString error;
            const ISerialFormat::ParseResult result = parseFunc(&m_buffer, &packet, &error);
            if (result == ISerialFormat::ParseResult::NeedMore)
                break;
            if (result == ISerialFormat::ParseResult::Invalid) {
                emit parseError(error);
                continue;
            }
            if (!packet.isEmpty())
                qDebug().noquote() << "Serial packet rawdata:" << QString::fromLatin1(packet.toHex(' '));
            if (m_packetQueue)
                m_packetQueue->push(packet);
        }
    }
}

SerialDecodeThread::SerialDecodeThread(PacketQueue* packetQueue, FrameQueue* frameQueue, QObject* parent)
    : QThread(parent)
    , m_packetQueue(packetQueue)
    , m_frameQueue(frameQueue) {}

void SerialDecodeThread::setDecodeFunc(DecodeFunc func) {
    QMutexLocker locker(&m_funcMutex);
    m_decodeFunc = std::move(func);
}

void SerialDecodeThread::stop() {
    m_running = false;
    if (m_packetQueue)
        m_packetQueue->stop();
}

void SerialDecodeThread::run() {
    m_running = true;
    while (m_running) {
        QByteArray packet;
        if (!m_packetQueue || !m_packetQueue->pop(&packet))
            break;

        DecodeFunc decodeFunc;
        {
            QMutexLocker locker(&m_funcMutex);
            decodeFunc = m_decodeFunc;
        }
        if (!decodeFunc)
            continue;

        DataFrame frame;
        QString error;
        if (!decodeFunc(packet, &frame, &error)) {
            emit decodeError(error);
            continue;
        }
        if (m_frameQueue)
            m_frameQueue->push(frame);
        emit frameAvailable();
    }
}

SerialSendWorker::SerialSendWorker(QObject* parent)
    : QObject(parent) {
    m_pollTimer.setParent(this);
    m_pollTimer.setTimerType(Qt::PreciseTimer);
    connect(&m_pollTimer, &QTimer::timeout, this, [this]() {
        sendRequest_();
    });
}

void SerialSendWorker::setTransport(std::unique_ptr<ISerialTransport> transport) {
    if (m_transport)
        m_transport->disconnect(this);
    m_transport = std::move(transport);
    if (m_transport) {
        connect(m_transport.get(), &ISerialTransport::bytesReceived,
                this, &SerialSendWorker::bytesReceived);
    }
}

void SerialSendWorker::setBuildRequestFunc(BuildRequestFunc func) {
    m_buildRequest = std::move(func);
}

void SerialSendWorker::setConfig(const SerialConfig& config) {
    m_config = config;
    updatePolling_();
}

void SerialSendWorker::setRequest(const SensorRequest& request) {
    m_request = request;
}

bool SerialSendWorker::openTransport(const SerialConfig& config, QString* error) {
    m_config = config;
    if (!m_transport) {
        if (error)
            *error = QStringLiteral("Transport missing");
        return false;
    }
    if (m_connected) {
        if (error)
            error->clear();
        updatePolling_();
        return true;
    }

    QString localError;
    const bool ok = m_transport->open(m_config, &localError);
    m_connected = ok;
    updatePolling_();
    if (error)
        *error = localError;
    return ok;
}

void SerialSendWorker::closeTransport() {
    if (!m_connected)
        return;
    m_pollTimer.stop();
    if (m_transport)
        m_transport->close();
    m_connected = false;
}

void SerialSendWorker::requestOnce() {
    sendRequest_();
}

void SerialSendWorker::updatePolling_() {
    if (!m_connected || m_config.mode != DeviceMode::Slave) {
        m_pollTimer.stop();
        return;
    }
    const int interval = qMax(1, m_config.pollIntervalMs);
    if (m_pollTimer.isActive())
        m_pollTimer.setInterval(interval);
    else
        m_pollTimer.start(interval);
}

void SerialSendWorker::sendRequest_() {
    if (!m_connected || !m_transport || !m_buildRequest)
        return;

    const QByteArray request = m_buildRequest(m_config, m_request);
    if (request.isEmpty())
        return;

    emit requestBuilt(request);

    QString error;
    if (!m_transport->writeBytes(request, &error))
        emit writeFailed(error);
}
