#include "bme280_app.h"

#include "mcc_generated_files/i2c_host/i2c1.h"
#include <stdint.h>
#include <stddef.h>

#define BME280_I2C_ADDRESS_LOW          0x76U
#define BME280_I2C_ADDRESS_HIGH         0x77U
#define BME280_REG_CHIP_ID              0xD0U
#define BME280_CHIP_ID                  0x60U
#define BME280_REG_RESET                0xE0U
#define BME280_SOFT_RESET_CMD           0xB6U
#define BME280_REG_CTRL_HUM             0xF2U
#define BME280_REG_STATUS               0xF3U
#define BME280_REG_CTRL_MEAS            0xF4U
#define BME280_REG_CONFIG               0xF5U
#define BME280_REG_PRESS_MSB            0xF7U
#define BME280_REG_CALIB00              0x88U
#define BME280_REG_CALIB26              0xE1U
#define BME280_REG_CALIB_H1             0xA1U
#define BME280_I2C_WAIT_LIMIT           100000U

struct BME280_CALIB_DATA
{
    uint16_t digT1;
    int16_t digT2;
    int16_t digT3;
    uint16_t digP1;
    int16_t digP2;
    int16_t digP3;
    int16_t digP4;
    int16_t digP5;
    int16_t digP6;
    int16_t digP7;
    int16_t digP8;
    int16_t digP9;
    uint8_t digH1;
    int16_t digH2;
    uint8_t digH3;
    int16_t digH4;
    int16_t digH5;
    int8_t digH6;
};

static struct BME280_CALIB_DATA bme280Calib;
static int32_t bme280Tfine = 0;
static bool bme280Initialized = false;
static uint16_t bme280I2cAddress = BME280_I2C_ADDRESS_LOW;
static uint8_t bme280LastChipId = 0U;
static const char *bme280LastError = "not initialized";

static void BME280_SetLastError(const char *errorText)
{
    bme280LastError = errorText;
}

static void BME280_SetLastI2CError(void)
{
    switch(I2C1_Host_ErrorGet())
    {
        case I2C_HOST_ERROR_NONE:
            BME280_SetLastError("none");
            break;
        case I2C_HOST_ERROR_NACK:
            BME280_SetLastError("i2c nack");
            break;
        case I2C_HOST_ERROR_BUS_COLLISION:
            BME280_SetLastError("i2c collision");
            break;
        default:
            BME280_SetLastError("i2c unknown");
            break;
    }
}

static bool BME280_WaitI2CIdle(void)
{
    uint32_t guardCount = 0U;

    while(I2C1_IsBusy())
    {
        guardCount++;
        if(guardCount > BME280_I2C_WAIT_LIMIT)
        {
            BME280_SetLastError("i2c busy timeout");
            return false;
        }
    }

    return true;
}

static bool BME280_WriteReg(uint8_t reg, uint8_t value)
{
    uint8_t packet[2] = {reg, value};

    if(!BME280_WaitI2CIdle())
    {
        return false;
    }

    if(!I2C1_Host_Write(bme280I2cAddress, packet, sizeof(packet)))
    {
        BME280_SetLastError("i2c request denied");
        return false;
    }

    if(!BME280_WaitI2CIdle())
    {
        return false;
    }

    BME280_SetLastI2CError();
    return (I2C1_Host_ErrorGet() == I2C_HOST_ERROR_NONE);
}

static bool BME280_ReadRegs(uint8_t reg, uint8_t *readData, size_t readLength)
{
    if((readData == NULL) || (readLength == 0U))
    {
        return false;
    }

    if(!BME280_WaitI2CIdle())
    {
        return false;
    }

    if(!I2C1_Host_WriteRead(bme280I2cAddress, &reg, 1U, readData, readLength))
    {
        BME280_SetLastError("i2c request denied");
        return false;
    }

    if(!BME280_WaitI2CIdle())
    {
        return false;
    }

    BME280_SetLastI2CError();
    return (I2C1_Host_ErrorGet() == I2C_HOST_ERROR_NONE);
}

static uint16_t BME280_U16LE(const uint8_t *raw)
{
    return (uint16_t)(((uint16_t)raw[1] << 8U) | raw[0]);
}

static int16_t BME280_S16LE(const uint8_t *raw)
{
    return (int16_t)BME280_U16LE(raw);
}

static bool BME280_LoadCalibration(void)
{
    uint8_t calib00[24] = {0};
    uint8_t calib26[7] = {0};

    if(!BME280_ReadRegs(BME280_REG_CALIB00, calib00, sizeof(calib00)))
    {
        return false;
    }

    if(!BME280_ReadRegs(BME280_REG_CALIB_H1, &bme280Calib.digH1, 1U))
    {
        return false;
    }

    if(!BME280_ReadRegs(BME280_REG_CALIB26, calib26, sizeof(calib26)))
    {
        return false;
    }

    bme280Calib.digT1 = BME280_U16LE(&calib00[0]);
    bme280Calib.digT2 = BME280_S16LE(&calib00[2]);
    bme280Calib.digT3 = BME280_S16LE(&calib00[4]);
    bme280Calib.digP1 = BME280_U16LE(&calib00[6]);
    bme280Calib.digP2 = BME280_S16LE(&calib00[8]);
    bme280Calib.digP3 = BME280_S16LE(&calib00[10]);
    bme280Calib.digP4 = BME280_S16LE(&calib00[12]);
    bme280Calib.digP5 = BME280_S16LE(&calib00[14]);
    bme280Calib.digP6 = BME280_S16LE(&calib00[16]);
    bme280Calib.digP7 = BME280_S16LE(&calib00[18]);
    bme280Calib.digP8 = BME280_S16LE(&calib00[20]);
    bme280Calib.digP9 = BME280_S16LE(&calib00[22]);

    bme280Calib.digH2 = BME280_S16LE(&calib26[0]);
    bme280Calib.digH3 = calib26[2];
    bme280Calib.digH4 = (int16_t)((((int16_t)(int8_t)calib26[3]) << 4) | (calib26[4] & 0x0FU));
    bme280Calib.digH5 = (int16_t)((((int16_t)(int8_t)calib26[5]) << 4) | ((calib26[4] >> 4) & 0x0FU));
    bme280Calib.digH6 = (int8_t)calib26[6];

    return true;
}

static int32_t BME280_CompensateTemp(int32_t adcT)
{
    int32_t var1;
    int32_t var2;

    var1 = ((((adcT >> 3) - ((int32_t)bme280Calib.digT1 << 1))) * ((int32_t)bme280Calib.digT2)) >> 11;
    var2 = (((((adcT >> 4) - ((int32_t)bme280Calib.digT1)) * ((adcT >> 4) - ((int32_t)bme280Calib.digT1))) >> 12) * ((int32_t)bme280Calib.digT3)) >> 14;
    bme280Tfine = var1 + var2;
    return (bme280Tfine * 5 + 128) >> 8;
}

static uint32_t BME280_CompensatePress(int32_t adcP)
{
    int64_t var1;
    int64_t var2;
    int64_t pressure;

    var1 = ((int64_t)bme280Tfine) - 128000LL;
    var2 = var1 * var1 * (int64_t)bme280Calib.digP6;
    var2 = var2 + ((var1 * (int64_t)bme280Calib.digP5) << 17);
    var2 = var2 + (((int64_t)bme280Calib.digP4) << 35);
    var1 = ((var1 * var1 * (int64_t)bme280Calib.digP3) >> 8) + ((var1 * (int64_t)bme280Calib.digP2) << 12);
    var1 = (((((int64_t)1) << 47) + var1) * ((int64_t)bme280Calib.digP1)) >> 33;

    if(var1 == 0)
    {
        return 0U;
    }

    pressure = 1048576LL - (int64_t)adcP;
    pressure = (((pressure << 31) - var2) * 3125LL) / var1;
    var1 = (((int64_t)bme280Calib.digP9) * (pressure >> 13) * (pressure >> 13)) >> 25;
    var2 = (((int64_t)bme280Calib.digP8) * pressure) >> 19;
    pressure = ((pressure + var1 + var2) >> 8) + (((int64_t)bme280Calib.digP7) << 4);

    return (uint32_t)pressure;
}

static uint32_t BME280_CompensateHum(int32_t adcH)
{
    int32_t value;

    value = bme280Tfine - 76800;
    value = (((((adcH << 14) - (((int32_t)bme280Calib.digH4) << 20) - (((int32_t)bme280Calib.digH5) * value)) + 16384) >> 15)
            * (((((((value * ((int32_t)bme280Calib.digH6)) >> 10) * (((value * ((int32_t)bme280Calib.digH3)) >> 11) + 32768)) >> 10) + 2097152)
            * ((int32_t)bme280Calib.digH2) + 8192) >> 14));
    value = value - (((((value >> 15) * (value >> 15)) >> 7) * ((int32_t)bme280Calib.digH1)) >> 4);

    if(value < 0)
    {
        value = 0;
    }
    if(value > 419430400)
    {
        value = 419430400;
    }

    return (uint32_t)(value >> 12);
}

bool BME280_AppInitialize(void)
{
    uint8_t chipId = 0U;
    uint8_t status = 0U;
    uint32_t guardCount = 0U;
    static const uint16_t bme280AddressCandidates[2] = {BME280_I2C_ADDRESS_LOW, BME280_I2C_ADDRESS_HIGH};
    uint8_t addrIndex;
    bool chipIdFound = false;

    bme280Initialized = false;
    bme280LastChipId = 0U;
    BME280_SetLastError("chip id read fail");

    for(addrIndex = 0U; addrIndex < (uint8_t)(sizeof(bme280AddressCandidates) / sizeof(bme280AddressCandidates[0])); addrIndex++)
    {
        bme280I2cAddress = bme280AddressCandidates[addrIndex];

        if(BME280_ReadRegs(BME280_REG_CHIP_ID, &chipId, 1U))
        {
            bme280LastChipId = chipId;
            chipIdFound = true;
            if(chipId == BME280_CHIP_ID)
            {
                break;
            }
        }
    }

    if(!chipIdFound)
    {
        bme280I2cAddress = BME280_I2C_ADDRESS_LOW;
        return false;
    }

    if(chipId != BME280_CHIP_ID)
    {
        BME280_SetLastError("chip id mismatch");
        return false;
    }

    if(!BME280_WriteReg(BME280_REG_RESET, BME280_SOFT_RESET_CMD))
    {
        BME280_SetLastError("reset failed");
        return false;
    }

    do
    {
        if(!BME280_ReadRegs(BME280_REG_STATUS, &status, 1U))
        {
            return false;
        }

        guardCount++;
        if(guardCount > BME280_I2C_WAIT_LIMIT)
        {
            BME280_SetLastError("reset timeout");
            return false;
        }
    }
    while((status & 0x01U) != 0U);

    if(!BME280_LoadCalibration())
    {
        BME280_SetLastError("calibration read fail");
        return false;
    }

    if(!BME280_WriteReg(BME280_REG_CTRL_HUM, 0x01U))
    {
        BME280_SetLastError("ctrl_hum write fail");
        return false;
    }

    if(!BME280_WriteReg(BME280_REG_CTRL_MEAS, 0x27U))
    {
        BME280_SetLastError("ctrl_meas write fail");
        return false;
    }

    if(!BME280_WriteReg(BME280_REG_CONFIG, 0xA0U))
    {
        BME280_SetLastError("config write fail");
        return false;
    }

    bme280Initialized = true;
    BME280_SetLastError("none");
    return true;
}

bool BME280_AppCalibrate(void)
{
    if(!bme280Initialized)
    {
        BME280_SetLastError("not initialized");
        return false;
    }

    if(!BME280_LoadCalibration())
    {
        BME280_SetLastError("calibration read fail");
        return false;
    }

    BME280_SetLastError("none");
    return true;
}

bool BME280_AppRead(float *temperatureC, float *humidityPct, float *pressurehPa)
{
    uint8_t raw[8] = {0};
    int32_t adcP;
    int32_t adcT;
    int32_t adcH;
    int32_t tempX100;
    uint32_t pressQ24_8;
    uint32_t humQ22_10;

    if((temperatureC == NULL) || (humidityPct == NULL) || (pressurehPa == NULL) || (!bme280Initialized))
    {
        if(!bme280Initialized)
        {
            BME280_SetLastError("not initialized");
        }
        return false;
    }

    if(!BME280_ReadRegs(BME280_REG_PRESS_MSB, raw, sizeof(raw)))
    {
        BME280_SetLastError("data read fail");
        return false;
    }

    adcP = (int32_t)((((uint32_t)raw[0] << 12U) | ((uint32_t)raw[1] << 4U) | ((uint32_t)raw[2] >> 4U)));
    adcT = (int32_t)((((uint32_t)raw[3] << 12U) | ((uint32_t)raw[4] << 4U) | ((uint32_t)raw[5] >> 4U)));
    adcH = (int32_t)(((uint32_t)raw[6] << 8U) | (uint32_t)raw[7]);

    tempX100 = BME280_CompensateTemp(adcT);
    pressQ24_8 = BME280_CompensatePress(adcP);
    humQ22_10 = BME280_CompensateHum(adcH);

    *temperatureC = ((float)tempX100) / 100.0f;
    *pressurehPa = ((float)pressQ24_8) / 25600.0f;
    *humidityPct = ((float)humQ22_10) / 1024.0f;

    BME280_SetLastError("none");
    return true;
}

uint16_t BME280_AppGetAddress(void)
{
    return bme280I2cAddress;
}

uint8_t BME280_AppGetLastChipId(void)
{
    return bme280LastChipId;
}

const char *BME280_AppGetLastErrorString(void)
{
    return bme280LastError;
}