// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later

/**
 * @file ads1115.cpp
 * @brief TI ADS1115 16-bit ADC codec - implementation. See ads1115.h.
 */

#include "services/ads1115/ads1115.h"
#include "ServerConfig.h"

#if DETWS_ENABLE_ADS1115

namespace
{
// Config-register field values (per the ADS1115 datasheet).
const uint16_t OS_SINGLE = 0x8000;   // start a single conversion
const uint16_t MUX_SINGLE0 = 0x4000; // single-ended AIN0; AINx = this | (channel << 12)
const uint16_t MODE_SINGLE = 0x0100; // single-shot
const uint16_t COMP_DISABLE = 0x0003;

// PGA bits and the matching full-scale range in microvolts, indexed by gain code.
const uint16_t PGA_BITS[6] = {0x0000, 0x0200, 0x0400, 0x0600, 0x0800, 0x0A00};
const int32_t FSR_UV[6] = {6144000, 4096000, 2048000, 1024000, 512000, 256000};
} // namespace

uint16_t ads1115_config_single(uint8_t channel, uint8_t gain, uint8_t dr)
{
    if (channel > 3)
        channel = 0;
    if (gain > ADS1115_GAIN_16)
        gain = (uint8_t)DETWS_ADS1115_GAIN;
    if (dr > ADS1115_DR_860)
        dr = (uint8_t)DETWS_ADS1115_DR;
    uint16_t cfg = OS_SINGLE;
#if DETWS_ADS1115_DIFFERENTIAL
    cfg |= (uint16_t)((uint16_t)channel << 12); // differential pair: MUX 0=AIN0-1, 1=AIN0-3, 2=AIN1-3, 3=AIN2-3
#else
    cfg |= (uint16_t)(MUX_SINGLE0 | ((uint16_t)channel << 12)); // single-ended AINx
#endif
    cfg |= PGA_BITS[gain];
    cfg |= MODE_SINGLE;
    cfg |= (uint16_t)((uint16_t)dr << 5); // data-rate bits [7:5]
    cfg |= COMP_DISABLE;
    return cfg;
}

int32_t ads1115_raw_to_uv(int16_t raw, uint8_t gain)
{
    if (gain > ADS1115_GAIN_16)
        gain = (uint8_t)DETWS_ADS1115_GAIN;
    return (int32_t)((int64_t)raw * FSR_UV[gain] / 32768);
}

// ---------------------------------------------------------------------------
// I2C binding
// ---------------------------------------------------------------------------

#if defined(ARDUINO)

#include "services/i2c.h"
#include <Arduino.h>
#include <Wire.h>

namespace
{
// All ADS1115 I2C-binding state, owned by one instance (internal linkage): the device
// address, so it is one named owner, unreachable from any other translation unit.
struct Ads1115Ctx
{
    uint8_t addr = DETWS_ADS1115_I2C_ADDR;
};
Ads1115Ctx s_ads;

bool wr16(uint8_t reg, uint16_t v)
{
    Wire.beginTransmission(s_ads.addr);
    Wire.write(reg);
    Wire.write((uint8_t)(v >> 8)); // ADS1115 registers are big-endian
    Wire.write((uint8_t)(v & 0xFF));
    return Wire.endTransmission() == 0;
}

bool rd16(uint8_t reg, uint16_t *v)
{
    Wire.beginTransmission(s_ads.addr);
    Wire.write(reg);
    if (Wire.endTransmission(false) != 0)
        return false;
    if (Wire.requestFrom((int)s_ads.addr, 2) != 2)
        return false;
    uint8_t hi = (uint8_t)Wire.read();
    uint8_t lo = (uint8_t)Wire.read();
    *v = (uint16_t)(((uint16_t)hi << 8) | lo);
    return true;
}
} // namespace

bool ads1115_begin(uint8_t addr)
{
    s_ads.addr = addr ? addr : (uint8_t)DETWS_ADS1115_I2C_ADDR;
    detws_i2c_begin();
    return true;
}

bool ads1115_read_raw(uint8_t channel, uint8_t gain, int16_t *raw)
{
    if (!raw)
        return false;
    uint8_t dr = (uint8_t)DETWS_ADS1115_DR;
    if (dr > ADS1115_DR_860)
        dr = ADS1115_DR_128;
    if (!wr16(ADS1115_REG_CONFIG, ads1115_config_single(channel, gain, dr)))
        return false;
    // Single-shot conversion time tracks the data rate (~1000/SPS ms); wait it out plus a 1 ms margin.
    static const uint16_t ads1115_sps[8] = {8, 16, 32, 64, 128, 250, 475, 860};
    delay(1000u / ads1115_sps[dr] + 1);
    uint16_t v = 0;
    if (!rd16(ADS1115_REG_CONVERSION, &v))
        return false;
    *raw = (int16_t)v;
    return true;
}

bool ads1115_read_uv(uint8_t channel, uint8_t gain, int32_t *microvolts)
{
    int16_t raw = 0;
    if (!ads1115_read_raw(channel, gain, &raw))
        return false;
    if (microvolts)
        *microvolts = ads1115_raw_to_uv(raw, gain);
    return true;
}

#else // host build: no I2C. The config encoder + conversion above are host-tested.

bool ads1115_begin(uint8_t)
{
    return false;
}
bool ads1115_read_raw(uint8_t, uint8_t, int16_t *)
{
    return false;
}
bool ads1115_read_uv(uint8_t, uint8_t, int32_t *)
{
    return false;
}

#endif // ARDUINO

#endif // DETWS_ENABLE_ADS1115
