#ifdef SENSECAP_INDICATOR

#include "I2CProxy.h"
#include "../IndicatorSerial.h"

I2CProxy *i2cProxy = new I2CProxy();

// Transaction state of the calling task. Slots are claimed on first use and
// never released: the set of tasks touching the bridged bus is fixed (main
// loop, UI task).
I2CProxy::Context &I2CProxy::ctx()
{
    TaskHandle_t self = xTaskGetCurrentTaskHandle();
    for (size_t i = 0; i < MAX_TASKS; i++) {
        if (_ctx[i].task == self)
            return _ctx[i];
    }
    // claiming a slot must be atomic: two tasks that pick the same one would
    // interleave their transactions in one buffer set
    Context *claimed = nullptr;
    portENTER_CRITICAL(&_claim_mux);
    for (size_t i = 0; i < MAX_TASKS && !claimed; i++) {
        if (_ctx[i].task == nullptr) {
            _ctx[i].task = self;
            claimed = &_ctx[i];
        }
    }
    portEXIT_CRITICAL(&_claim_mux);
    if (claimed)
        return *claimed;
    LOG_WARN("I2CProxy: more tasks than contexts, sharing the last one");
    return _ctx[MAX_TASKS - 1];
}

void I2CProxy::beginTransmission(uint8_t address)
{
    Context &c = ctx();
    c.txAddress = address;
    c.txLen = 0;
    c.txPending = true;
}

size_t I2CProxy::write(uint8_t data)
{
    Context &c = ctx();
    if (!c.txPending || c.txLen >= MAX_WRITE)
        return 0;
    c.txBuf[c.txLen++] = data;
    return 1;
}

size_t I2CProxy::write(const uint8_t *data, size_t len)
{
    size_t n = 0;
    while (n < len && write(data[n]))
        n++;
    return n;
}

uint8_t I2CProxy::endTransmission(bool stopBit)
{
    Context &c = ctx();
    if (!stopBit) {
        // Keep the buffered write pending, it is combined with the following
        // requestFrom() into a single write+read transaction
        return 0;
    }
    uint8_t rv = transact(c, c.txAddress, 0);
    c.txLen = 0;
    c.txPending = false;
    return rv;
}

size_t I2CProxy::requestFrom(uint8_t address, size_t len, bool stopBit)
{
    (void)stopBit;
    Context &c = ctx();
    if (len > MAX_READ)
        len = MAX_READ;

    // a write pending for this address is executed together with the read as
    // one transaction with repeated start
    if (!c.txPending || c.txAddress != address)
        c.txLen = 0;

    uint8_t rv = transact(c, address, len);
    c.txLen = 0;
    c.txPending = false;
    return rv == 0 ? c.rxLen : 0;
}

int I2CProxy::available()
{
    const Context &c = ctx();
    return (int)(c.rxLen - c.rxPos);
}

int I2CProxy::read()
{
    Context &c = ctx();
    return c.rxPos < c.rxLen ? c.rxBuf[c.rxPos++] : -1;
}

int I2CProxy::peek()
{
    const Context &c = ctx();
    return c.rxPos < c.rxLen ? c.rxBuf[c.rxPos] : -1;
}

// Run one tunneled transaction, returns TwoWire endTransmission error codes:
// 0 success, 1 data too long, 2 NACK on address, 3 NACK on data, 4 other, 5 timeout
uint8_t I2CProxy::transact(Context &c, uint8_t address, size_t rlen)
{
    c.rxLen = 0;
    c.rxPos = 0;

    if (!sensecapIndicator)
        return 4;
    if (c.txLen > MAX_WRITE)
        return 1;

    meshtastic_I2CResult result = meshtastic_I2CResult_init_zero;
    if (!sensecapIndicator->i2c_transact(address, c.txBuf, c.txLen, rlen, &result))
        return 5;

    switch (result.status) {
    case meshtastic_I2CResult_Status_OK:
        c.rxLen = result.read_data.size > MAX_READ ? MAX_READ : result.read_data.size;
        memcpy(c.rxBuf, result.read_data.bytes, c.rxLen);
        return 0;
    case meshtastic_I2CResult_Status_NACK_ADDRESS:
        return 2;
    case meshtastic_I2CResult_Status_NACK_DATA:
        return 3;
    default:
        // includes UNSPECIFIED: an empty result must not read as success
        return 4;
    }
}

#endif // SENSECAP_INDICATOR
