/*
    Triple buffering data structure

    Example writing:
    ----------------

    DMXTripleBuffer buffer;
    
    bytes_written = producer.write(
        buffer.fill_buffer + buffer.fill_pos,  // destination pointer
        512 - buffer.fill_pos                  // maximum allowed
    );

    buffer.fill_pos += bytes_written;

    if buffer.fill_pos == 512 {
        buffer.on_fill_complete(); // swap buffers
        buffer.fill_pos = 0;       // reset fill pos
    }
 */

struct InternalBuffer
{
    char data[512] = {0};
    bool is_fresh = false;
};

class DMXTripleBuffer
{
public:
    DMXTripleBuffer()
    {
        fill_buffer = &_buffers[0];
        drain_buffer = &_buffers[1];
        off_buffer = &_buffers[2];
    }

    void on_fill_complete()
    {
        fill_pos = 0;
        fill_buffer->is_fresh = true;
        std::swap(fill_buffer, off_buffer);
    }

    void on_drain_complete()
    {
        drain_pos = 0;
        drain_buffer->is_fresh = false;
        if (off_buffer->is_fresh)
        {
            std::swap(drain_buffer, off_buffer);
        }
    }
    InternalBuffer *fill_buffer, *drain_buffer, *off_buffer;
    size_t fill_pos = 0, drain_pos = 0;

private:
    InternalBuffer _buffers[3];
};

/*
    Some globals
 */

DMXTripleBuffer buffer;   // Triple buffer instance
bool packet_ready = true; // flag to write header

// send a "Sync." every second if no data is coming in
unsigned long time_since_last_sync;
const unsigned long SYNC_TIMEOUT = 1000;

/*
    setup

    initialize both Serial connections and write the initial "Sync."
 */

void setup()
{
    Serial.begin(500000);        // USB
    Serial.setRxBufferSize(512); // to fit the full DMX packet. (default is 128)
    while (!Serial)
    {
        // spin until serial is up
    }

    Serial1.begin(250000, SERIAL_8N2); // DMX
    while (!Serial1)
    {
        // spin until serial1 is up
    }

    Serial.println();
    Serial.println("Sync.");
    time_since_last_sync = millis();
}

/*
    loop

    continuously poll Serial1 for writing and Serial for reading
    only read/write as much as fits into the UART buffers to avoid blocking

    since Serial1 is set to 250000 baud, it should be able to write 50 DMX packets per second
 */

void loop()
{

    // output

    if (packet_ready)
    {
        send_dmx_header();
        packet_ready = false;
    }
    size_t n = Serial1.availableForWrite();

    size_t written = Serial1.write(
        buffer.drain_buffer->data + buffer.drain_pos,
        std::min(n, 512 - buffer.drain_pos));

    buffer.drain_pos += written;

    if (buffer.drain_pos == 512)
    {
        buffer.on_drain_complete();
        packet_ready = true;
    }

    // input

    n = Serial.available();

    unsigned long now = millis();

    if (!n)
    {
        if (now - time_since_last_sync > 1000)
        {
            buffer.fill_pos = 0;
            Serial.println("Sync.");
            time_since_last_sync = now;
        }
        return;
    }

    time_since_last_sync = now;

    size_t read = Serial.read(
        buffer.fill_buffer->data + buffer.fill_pos,
        std::min(n, 512 - buffer.fill_pos));

    buffer.fill_pos += read;

    if (buffer.fill_pos == 512)
    {
        buffer.on_fill_complete();
        Serial.println("Ack.");
    }
}

void send_dmx_header()
{
    Serial1.flush();
    Serial1.begin(90000, SERIAL_8N2);
    // send the break as a "slow" byte
    Serial1.write(0);
    // switch back to the original baud rate
    Serial1.flush();
    Serial1.begin(250000, SERIAL_8N2);
    Serial1.write(0); // Start-Byte
}