/* Copyright (c) 2025, Canaan Bright Sight Co., Ltd
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <cstddef>
#include <cstdint>
#include <cstdio>

#include "api/Wire.h"
#include "drv_fpioa.h"

#if SOC_I2C_SUPPORT_SLAVE
#endif

#define i2cIsInit(num) (NULL != i2c)

namespace arduino {

TwoWire::TwoWire(uint8_t bus_num)
  : num(bus_num), sda(-1), scl(-1), bufferSize(I2C_BUFFER_LENGTH), i2c(NULL)  // default Wire Buffer Size
    ,
    rxBuffer(NULL), rxIndex(0), rxLength(0), txBuffer(NULL), txLength(0), txAddress(0), nonStop(false)
#if !CONFIG_DISABLE_HAL_LOCKS
    ,
    curr_tid(-1), lock(NULL)
#endif
#if SOC_I2C_SUPPORT_SLAVE
#endif
{
}

TwoWire::~TwoWire() {
    end();
#if !CONFIG_DISABLE_HAL_LOCKS
    if (NULL != lock) {
        pthread_mutex_destroy(lock);
    }
#endif
}

uint8_t TwoWire::getBusNum() {
    return num;
}

bool TwoWire::initPins(int sdaPin, int sclPin) {

#define I2C_SCL_FUNC(id)        \
    ((id) == 0   ? IIC0_SCL     \
     : (id) == 1 ? IIC1_SCL     \
     : (id) == 2 ? IIC2_SCL     \
     : (id) == 3 ? IIC3_SCL     \
     : (id) == 4 ? IIC4_SCL     \
     : FUNC_MAX)

#define I2C_SDA_FUNC(id)        \
    ((id) == 0   ? IIC0_SDA     \
     : (id) == 1 ? IIC1_SDA     \
     : (id) == 2 ? IIC2_SDA     \
     : (id) == 3 ? IIC3_SDA     \
     : (id) == 4 ? IIC4_SDA     \
     : FUNC_MAX)

#define I2C_SCL_PIN(id)         \
    ((id) == 0   ? 32           \
     : (id) == 1 ? 9           \
     : (id) == 2 ? 11           \
     : (id) == 3 ? 36           \
     : (id) == 4 ? 7            \
     : -1)

#define I2C_SDA_PIN(id)         \
    ((id) == 0   ? 33           \
     : (id) == 1 ? 10           \
     : (id) == 2 ? 12           \
     : (id) == 3 ? 37           \
     : (id) == 4 ? 8            \
     : -1)

    int ret = 0;
    fpioa_err_t err = FPIOA_OK;

    if (num > 4 || num < 0) {
        printf("Invalid i2c id for I2C%d\n", num);
        return false;
    }

    if (sdaPin < 0) {  // default param passed
        if (sda == -1) {
            sdaPin = I2C_SDA_PIN(num);  //use Default Pin
        } else {
            sdaPin = sda;  // reuse prior pin
        }
    }

    if (sclPin < 0) {  // default param passed
        if (scl == -1) {
            sclPin = I2C_SCL_PIN(num);  // use Default pin
        } else {
            sclPin = scl;  // reuse prior pin
        }
    }

    fpioa_func_t f_scl = I2C_SCL_FUNC(num);
    if (FPIOA_OK != (err = drv_fpioa_validate_pin(sclPin, f_scl))) {
        printf("Invalid scl pin %d for I2C%d\n", sclPin, num);
        return false;
    }

    fpioa_func_t f_sda = I2C_SDA_FUNC(num);
    if (FPIOA_OK != (err = drv_fpioa_validate_pin(sdaPin, f_sda))) {
        printf("Invalid sda pin %d for I2C%d\n", sdaPin, num);
        return false;
    }

    sda = sdaPin;
    scl = sclPin;

    return true;
}

bool TwoWire::setPins(int sdaPin, int sclPin) {
#if !CONFIG_DISABLE_HAL_LOCKS
    if (lock == NULL) {
        lock = &_lock;
        if (0 != pthread_mutex_init(lock, NULL)) {
            printf("pthread_mutex_init failed\n");
            return false;
        }
    }
    //acquire lock
    if (0 != pthread_mutex_lock(lock)) {
        printf("could not acquire lock\n");
        return false;
    }
#endif

    if (!i2cIsInit(num)) {
        initPins(sdaPin, sclPin);
    } else {
        printf("bus already initialized. change pins only when not.\n");
    }

#if !CONFIG_DISABLE_HAL_LOCKS
    //release lock
    pthread_mutex_unlock(lock);
#endif

    return !i2cIsInit(num);
}

bool TwoWire::allocateWireBuffer() {
    // or both buffer can be allocated or none will be
    if (rxBuffer == NULL) {
        rxBuffer = (uint8_t *)malloc(bufferSize);
        if (rxBuffer == NULL) {
            printf("Can't allocate memory for I2C_%d rxBuffer\n", num);
            return false;
        }
    }
    if (txBuffer == NULL) {
        txBuffer = (uint8_t *)malloc(bufferSize);
        if (txBuffer == NULL) {
            printf("Can't allocate memory for I2C_%d txBuffer\n", num);
            freeWireBuffer();  // free rxBuffer for safety!
            return false;
        }
    }
    // in case both were allocated before, they must have the same size. All good.
    return true;
}


void TwoWire::freeWireBuffer() {
    if (rxBuffer != NULL) {
        free(rxBuffer);
        rxBuffer = NULL;
    }
    if (txBuffer != NULL) {
        free(txBuffer);
        txBuffer = NULL;
    }
}

size_t TwoWire::setBufferSize(size_t bSize) {
    // Maximum size .... HEAP limited ;-)
    if (bSize < 32) {  // 32 bytes is the I2C FIFO Len for ESP32/S2/S3/C3
        printf("Minimum Wire Buffer size is 32 bytes\n");
        return 0;
    }

#if !CONFIG_DISABLE_HAL_LOCKS
    if (lock == NULL) {
        lock = &_lock;
        if (0 != pthread_mutex_init(lock, NULL)) {
            printf("pthread_mutex_init failed\n");
            return 0;
        }
    }
    //acquire lock
    if (0 != pthread_mutex_lock(lock)) {
        printf("could not acquire lock\n");
        return 0;
    }
#endif

    // allocateWireBuffer allocates memory for both pointers or just free them
    if (rxBuffer != NULL || txBuffer != NULL) {
        // if begin() has been already executed, memory size changes... data may be lost. We don't care! :^)
        if (bSize != bufferSize) {
            // we want a new buffer size ... just reset buffer pointers and allocate new ones
            freeWireBuffer();
            bufferSize = bSize;
            if (!allocateWireBuffer()) {
                // failed! Error message already issued
                bSize = 0;  // returns error
                printf("Buffer allocation failed\n");
            }
        }  // else nothing changes, all set!
    } else {
        // no memory allocated yet, just change the size value - allocation in begin()
        bufferSize = bSize;
    }

#if !CONFIG_DISABLE_HAL_LOCKS
    //release lock
    pthread_mutex_unlock(lock);
#endif

    return bSize;
}

#if SOC_I2C_SUPPORT_SLAVE
// Slave Begin
bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency) {
}
#endif

// Master Begin
bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) {
    bool started = false;
    int err = 0;

#if !CONFIG_DISABLE_HAL_LOCKS
    if (lock == NULL) {
        lock = &_lock;
        if (0 != pthread_mutex_init(lock, NULL)) {
            printf("pthread_mutex_init failed\n");
            return false;
        }
    }
    //acquire lock
    if (0 != pthread_mutex_lock(lock)) {
        printf("could not acquire lock\n");
        return false;
    }
#endif

#if SOC_I2C_SUPPORT_SLAVE
#endif
    if (i2cIsInit(num)) {
        printf("Bus already started in Master Mode.\n");
        started = true;
        goto end;
    }

    if (!allocateWireBuffer()) {
        // failed! Error Message already issued
        goto end;
    }

    if (!initPins(sdaPin, sclPin)) {
        goto end;
    }

    err = drv_i2c_inst_create(num, frequency, 1000, scl, sda, &i2c);
    started = (err == 0);

end:
    if (!started) {
        freeWireBuffer();
    }

#if !CONFIG_DISABLE_HAL_LOCKS
    //release lock
    pthread_mutex_unlock(lock);
#endif

    return started;
}

bool TwoWire::end() {
#if !CONFIG_DISABLE_HAL_LOCKS
    if (lock != NULL) {
        //acquire lock
        if (0 != pthread_mutex_lock(lock)) {
            printf("could not acquire lock\n");
            return false;
        }
#endif
#if SOC_I2C_SUPPORT_SLAVE
#endif
        drv_i2c_inst_destroy(&i2c);
        freeWireBuffer();
#if !CONFIG_DISABLE_HAL_LOCKS
        //release lock
        pthread_mutex_unlock(lock);
    }
#endif

    return true;
}

uint32_t TwoWire::getClock() {
    uint32_t frequency = 0;
#if !CONFIG_DISABLE_HAL_LOCKS
    //acquire lock
    if (lock == NULL || 0 != pthread_mutex_lock(lock)) {
        printf("could not acquire lock\n");
    } else {
#endif

#if SOC_I2C_SUPPORT_SLAVE
#endif
        {
            frequency = drv_i2c_master_get_freq(i2c);
        }
#if !CONFIG_DISABLE_HAL_LOCKS
        //release lock
        pthread_mutex_unlock(lock);
    }
#endif

    return frequency;
}

bool TwoWire::setClock(uint32_t frequency) {
    int err = 0;

#if !CONFIG_DISABLE_HAL_LOCKS
    //acquire lock
    if (lock == NULL || 0 != pthread_mutex_lock(lock)) {
        printf("could not acquire lock\n");
        return false;
    }
#endif

#if SOC_I2C_SUPPORT_SLAVE
#endif
    {
        err = drv_i2c_set_freq(i2c, frequency);
    }

#if !CONFIG_DISABLE_HAL_LOCKS
    //release lock
    pthread_mutex_unlock(lock);
#endif

    return (err == 0);
}

void TwoWire::setTimeOut(uint16_t timeOutMillis) {
  drv_i2c_set_timeout(i2c, timeOutMillis);
}

uint16_t TwoWire::getTimeOut() {
  return drv_i2c_master_get_timeout_ms(i2c);
}

void TwoWire::beginTransmission(uint8_t address) {
#if SOC_I2C_SUPPORT_SLAVE
#endif

#if !CONFIG_DISABLE_HAL_LOCKS
    pthread_t tid = pthread_self();
    if (curr_tid != tid) {
        //acquire lock
        if (lock == NULL || 0 != pthread_mutex_lock(lock)) {
            printf("could not acquire lock\n");
            return;
        }
        curr_tid = tid;
    }
#endif
    nonStop = false;
    txAddress = address;
    txLength = 0;
}

/*
https://www.arduino.cc/reference/en/language/functions/communication/wire/endtransmission/
endTransmission() returns:
0: success.
1: data too long to fit in transmit buffer.
2: received NACK on transmit of address.
3: received NACK on transmit of data.
4: other error.
5: timeout
*/
uint8_t TwoWire::endTransmission(bool sendStop) {
#if SOC_I2C_SUPPORT_SLAVE
#endif
    int err = 0;

    if (txBuffer == NULL) {
        printf("NULL TX buffer pointer\n");
        return 4;
    }

    if (sendStop) {
        /* TODO: scan device */
        if (txLength == 0) {
            txLength = 1;
        }
        i2c_msg_t msg = { .addr = txAddress, .flags = DRV_I2C_WR, .len = (uint16_t)txLength, .buf = txBuffer};
        err = drv_i2c_transfer(i2c, &msg, 1);

#if !CONFIG_DISABLE_HAL_LOCKS
        curr_tid = NULL;
        //release lock
        pthread_mutex_unlock(lock);
#endif
    } else {
        nonStop = true;
    }

    switch (err) {
    case 0:         return 0;
    case -2:        return 5;
    default:        break;
    }

    return 4;
}

uint8_t TwoWire::endTransmission() {
    return endTransmission(true);
}

size_t TwoWire::requestFrom(uint8_t address, size_t size, bool sendStop) {
#if SOC_I2C_SUPPORT_SLAVE
#endif
    if (rxBuffer == NULL || txBuffer == NULL) {
        printf("NULL buffer pointer\n");
        return 0;
    }
    int err = 0;

#if !CONFIG_DISABLE_HAL_LOCKS
    pthread_t tid = pthread_self();
    if (curr_tid != tid) {
        //acquire lock
        if (lock == NULL || 0 != pthread_mutex_lock(lock)) {
            printf("could not acquire lock\n");
            return 0;
        }
        curr_tid = tid;
    }
#endif

    if (nonStop) {
        if (address != txAddress) {
            printf("Unfinished Repeated Start transaction! Expected address do not match! %u != %u\n", address, txAddress);
#if !CONFIG_DISABLE_HAL_LOCKS
            curr_tid = NULL;
            //release lock
            pthread_mutex_unlock(lock);
#endif
            return 0;
        }
        nonStop = false;
        rxIndex = 0;
        rxLength = 0;
        i2c_msg_t msg[2] = {
            { .addr = address, .flags = DRV_I2C_WR, .len = (uint16_t)txLength, .buf = txBuffer},
            { .addr = address, .flags = DRV_I2C_RD, .len = (uint16_t)size, .buf = rxBuffer},
        };
        err = drv_i2c_transfer(i2c, &msg[0], 2);
        if (err) {
            printf("i2c transfer returned Error %d\n", err);
        }
    } else {
        rxIndex = 0;
        rxLength = 0;
        i2c_msg_t msg = { .addr = address, .flags = DRV_I2C_RD, .len = (uint16_t)size, .buf = rxBuffer };
        err = drv_i2c_transfer(i2c, &msg, 1);
        if (err) {
            printf("i2cRead returned Error %d\n", err);
        }
    }
    rxLength = size;

#if !CONFIG_DISABLE_HAL_LOCKS
    curr_tid = NULL;
    //release lock
    pthread_mutex_unlock(lock);
#endif

    return rxLength;
}

size_t TwoWire::requestFrom(uint8_t address, size_t size) {
    return requestFrom(address, size, true);
}

size_t TwoWire::write(uint8_t data) {
    if (txBuffer == NULL) {
        printf("NULL TX buffer pointer\n");
        return 0;
    }
    if (txLength >= bufferSize) {
        return 0;
    }
    txBuffer[txLength++] = data;

    return 1;
}

size_t TwoWire::write(const uint8_t *data, size_t quantity) {
    for (size_t i = 0; i < quantity; ++i) {
        if (!write(data[i])) {
            return i;
        }
    }
    return quantity;
}

int TwoWire::available() {
    int result = rxLength - rxIndex;
    return result;
}

int TwoWire::read() {
    int value = -1;

    if (rxBuffer == NULL) {
        printf("NULL RX buffer pointer\n");
        return value;
    }
    if (rxIndex < rxLength) {
        value = rxBuffer[rxIndex++];
    }

    return value;
}

int TwoWire::peek() {
    int value = -1;

    if (rxBuffer == NULL) {
        printf("NULL RX buffer pointer\n");
        return value;
    }
    if (rxIndex < rxLength) {
        value = rxBuffer[rxIndex];
    }

    return value;
}

void TwoWire::flush() {
    rxIndex = 0;
    rxLength = 0;
    txLength = 0;
    //i2cFlush(num); // cleanup
}

void TwoWire::onReceive(const std::function<void(int)> &function) {
#if SOC_I2C_SUPPORT_SLAVE
    user_onReceive = function;
#endif
}

// sets function called on slave read
void TwoWire::onRequest(const std::function<void()> &function) {
#if SOC_I2C_SUPPORT_SLAVE
    user_onRequest = function;
#endif
}

#if SOC_I2C_SUPPORT_SLAVE

size_t TwoWire::slaveWrite(const uint8_t *buffer, size_t len) {
  return i2cSlaveWrite(num, buffer, len, _timeOutMillis);
}

void TwoWire::onReceiveService(uint8_t num, uint8_t *inBytes, size_t numBytes, bool stop, void *arg) {
    TwoWire *wire = (TwoWire *)arg;
    if (!wire->user_onReceive) {
        return;
    }
    if (wire->rxBuffer == NULL) {
        printf("NULL RX buffer pointer\n");
        return;
    }
    for (uint8_t i = 0; i < numBytes; ++i) {
        wire->rxBuffer[i] = inBytes[i];
    }
    wire->rxIndex = 0;
    wire->rxLength = numBytes;
    wire->user_onReceive(numBytes);
}

void TwoWire::onRequestService(uint8_t num, void *arg) {
    TwoWire *wire = (TwoWire *)arg;
    if (!wire->user_onRequest) {
        return;
    }
    if (wire->txBuffer == NULL) {
        log_e("NULL TX buffer pointer\n");
        return;
    }
    wire->txLength = 0;
    wire->user_onRequest();
    if (wire->txLength) {
        wire->slaveWrite((uint8_t *)wire->txBuffer, wire->txLength);
    }
}

#endif

TwoWire Wire =  TwoWire(0);
TwoWire Wire1 = TwoWire(1);
TwoWire Wire2 = TwoWire(2);
TwoWire Wire3 = TwoWire(3);
TwoWire Wire4 = TwoWire(4);
} // namespace arduino
