#ifndef DATARECEIVER_H
#define DATARECEIVER_H

#include <QObject>
#include <QUdpSocket>
#include <QTimer>
#include <QThread>
#include <QSemaphore>
#include <cstring> // For std::memcpy

#include "cbuffer.h"

#define DATA_PACKET_HEADER_SIZE     6
#define DATA_SYNC_HEADER_SIZE       6
#define DATA_RMS_FRAME_SIZE         16  // 8 unsigned shorts
#define DATA_BYTES_PER_SAMPLE       8   // RAW1RAW2+CAL1+CAL2
#define DATA_SAMPLES_PER_SENSOR     32 //grab 2 channels per sample RAW1+RAW2
#define DATA_MAX_SENSORS_PER_BOARD  5
#define DATA_MAX_BUNCH              16  // Max. product of dmaBunch * ethBunch

//#define DATA_BLOCK_SIZE             (DATA_SENSORS_PER_BOARD * DATA_SAMPLES_PER_SENSOR * DATA_BYTES_PER_SAMPLE)
#define DATA_BLOCK_SIZE             (sensorsPerBoard * DATA_SAMPLES_PER_SENSOR * DATA_BYTES_PER_SAMPLE)
#define DATA_PACKET_SIZE            ( DATA_MAX_BUNCH * (DATA_PACKET_HEADER_SIZE + DATA_SYNC_HEADER_SIZE + DATA_BLOCK_SIZE + DATA_RMS_FRAME_SIZE ) )

#define DATA_MAX_BLOCK_SIZE         (DATA_MAX_SENSORS_PER_BOARD * DATA_SAMPLES_PER_SENSOR * DATA_BYTES_PER_SAMPLE)
#define DATA_MAX_PACKET_SIZE        ( DATA_MAX_BUNCH * (DATA_PACKET_HEADER_SIZE + DATA_SYNC_HEADER_SIZE + DATA_MAX_BLOCK_SIZE + DATA_RMS_FRAME_SIZE ) )

#define RECEIVER_BUFFER_SIZE        10000

#define RECEIVER_TIMER_PERIOD_MS    200 // The period of the timer to measure data rate. The measurement is always properly scaled.
#define RECEIVER_FRAMES_PER_SIG     100 // The DataReady signal is transmitted only every N frames, not to overload queued signals framework.

typedef struct SyncFrame
{
    // unsigned short channel_id;
    unsigned short local_ctr;
    unsigned short global_ctr;
    unsigned short sma_state;
    // These fields are additional compared to STM side
    unsigned short dummy = 0xFFFF;  // For nice structure packing
    int device_nr;
    int data_ok;
} SyncFrame;

typedef struct {
    unsigned short mean;
    unsigned short sigma;
    unsigned short max;
    unsigned short status;
    unsigned short registers[4];
} RMSFrame;

class BufferData
{
public:
    SyncFrame sync_frame;
    RMSFrame rms_frame;
    int buffer_size;

    struct SensorData {
        unsigned short raw1;  // First 16-bit unsigned short
        unsigned short raw2;  // Second 16-bit unsigned short
        signed short cal1;           // First 16-bit signed short
        signed short cal2;           // Second 16-bit signed short
    };
    SensorData* sensorData; // Renamed from sensor_data

    BufferData() : buffer_size(0), sensorData(nullptr) {}

    BufferData(int size) : buffer_size(0), sensorData(nullptr)
    {
        resize(size);
    }

    void resize(int size)
    {
        if (size == buffer_size)
            return; // No need to change
        if (sensorData)
        {
            delete[] sensorData;
            sensorData = nullptr;
        }
        buffer_size = size;
        if (size) // Do not allocate memory for an empty buffer
            sensorData = new SensorData[size];
    }

    BufferData(const BufferData& master) : buffer_size(0), sensorData(nullptr)
    {
        sync_frame = master.sync_frame;
        rms_frame = master.rms_frame;
        resize(master.buffer_size);
        std::memcpy(sensorData, master.sensorData, buffer_size * sizeof(SensorData));
    }

    BufferData& operator=(const BufferData& master)
    {
        if (this == &master)
            return *this; // Self-assignment
        sync_frame = master.sync_frame;
        rms_frame = master.rms_frame;
        resize(master.buffer_size);
        std::memcpy(sensorData, master.sensorData, buffer_size * sizeof(SensorData));
        return *this;
    }

    ~BufferData()
    {
        resize(0);  // :)
    }
};

typedef CBuffer<BufferData> DataBuffer;

class DataReceiver : public QObject
{
    Q_OBJECT
public:
    explicit DataReceiver(QObject *parent = nullptr);
    ~DataReceiver();

    void configureEthSettings(QHostAddress address_to_set, quint16 port_to_set);
    void configureBunchSize(int dma, int eth);
    void outputEnable(int en);

    int frameRate = 0;
    int devNr = 0;
    int sensorsPerBoard = 5;

    DataBuffer dataBuffer;

signals:
    void sigInit();
    void sigDeinit();
    void sigConfigureEthSettings();
    void sigDataReady(DataReceiver* ptr);

public slots:
    void onTimer();

protected:
    void init();
    void deinit();
    QThread thread;
    QSemaphore initSemaphore;
    QTimer* timer = nullptr;
    QUdpSocket* dataSocket = nullptr;
    QHostAddress address;
    quint16 port;
    int outputEnabled = 0;
    int dmaBunch = 1;
    int ethBunch = 1;
    char tmpBuffer[DATA_MAX_PACKET_SIZE];
    int framesReceived = 0; // To calculate frame rate
    int framesFromLastSig = 0;

protected slots:
    void readData();
    void onInit();
    void onDeinit();
    void onConfigureEthSettings();
};

#endif // DATARECEIVER_H
