#ifndef TACTILEIPC3D_SERIAL_THREADS_H
#define TACTILEIPC3D_SERIAL_THREADS_H

#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QQueue>
#include <QTimer>
#include <QByteArray>
#include <QString>
#include <atomic>
#include <functional>
#include <memory>

#include "serial_format.h"
#include "serial_transport.h"
#include "serial_queue.h"
#include "serial_types.h"

class SerialReadThread : public QThread {
    Q_OBJECT
public:
    using ParseFunc = std::function<ISerialFormat::ParseResult(QByteArray*, QByteArray*, QString*)>;

    explicit SerialReadThread(PacketQueue* packetQueue, QObject* parent = nullptr);

    void setParseFunc(ParseFunc func);
    void enqueueBytes(const QByteArray& data);
    void clear();
    void stop();

signals:
    void parseError(const QString& error);

protected:
    void run() override;

private:
    PacketQueue* m_packetQueue = nullptr;

    QMutex m_queueMutex;
    QWaitCondition m_dataReady;
    QQueue<QByteArray> m_byteQueue;

    QMutex m_funcMutex;
    ParseFunc m_parseFunc;

    QByteArray m_buffer;
    std::atomic_bool m_running{false};
};

class SerialDecodeThread : public QThread {
    Q_OBJECT
public:
    using DecodeFunc = std::function<bool(const QByteArray&, DataFrame*, QString*)>;

    explicit SerialDecodeThread(PacketQueue* packetQueue, FrameQueue* frameQueue, QObject* parent = nullptr);

    void setDecodeFunc(DecodeFunc func);
    void stop();

signals:
    void frameAvailable();
    void decodeError(const QString& error);

protected:
    void run() override;

private:
    PacketQueue* m_packetQueue = nullptr;
    FrameQueue* m_frameQueue = nullptr;

    QMutex m_funcMutex;
    DecodeFunc m_decodeFunc;
    std::atomic_bool m_running{false};
};

class SerialSendWorker : public QObject {
    Q_OBJECT
public:
    using BuildRequestFunc = std::function<QByteArray(const SerialConfig&, const SensorRequest&)>;

    explicit SerialSendWorker(QObject* parent = nullptr);

    void setTransport(std::unique_ptr<ISerialTransport> transport);
    void setBuildRequestFunc(BuildRequestFunc func);
    void setConfig(const SerialConfig& config);
    void setRequest(const SensorRequest& request);

    bool openTransport(const SerialConfig& config, QString* error);
    void closeTransport();
    void requestOnce();

signals:
    void bytesReceived(const QByteArray& data);
    void requestBuilt(const QByteArray& data);
    void writeFailed(const QString& error);

private:
    void updatePolling_();
    void sendRequest_();

    SerialConfig m_config;
    SensorRequest m_request;
    BuildRequestFunc m_buildRequest;

    std::unique_ptr<ISerialTransport> m_transport;
    QTimer m_pollTimer;
    bool m_connected = false;
};

#endif // TACTILEIPC3D_SERIAL_THREADS_H
