/**
 * @file adc.cpp
 * @brief ADC wrapper component implementation
 * @author Mahmoud Elmohtady
 * @company Nabd solutions - ASF
 * @copyright Copyright (c) 2025
 */

#include "adc.hpp"
#include "logger.hpp"
#include <cstring>

static const char* TAG = "ADC_WRAPPER";

Adc::Adc()
{
    // Initialize handles to NULL
    memset(m_adcHandle_, 0, sizeof(m_adcHandle_));
    memset(m_caliHandle_, 0, sizeof(m_caliHandle_));
    memset(m_unitInitialized_, false, sizeof(m_unitInitialized_));
    memset(m_channelConfigured_, false, sizeof(m_channelConfigured_));
    
    m_unitBitwidth_[0] = AdcBitwidth::WIDTH_12BIT;
    m_unitBitwidth_[1] = AdcBitwidth::WIDTH_12BIT;
    
    ASF_LOGI(TAG, 2000, asf::logger::Criticality::LOW, "ADC wrapper initialized");
}

Adc::~Adc()
{
    // Deinitialize all units
    for (int i = 0; i < 2; i++) {
        if (m_unitInitialized_[i]) {
            deinitializeUnit(static_cast<AdcUnit>(i + 1));
        }
    }
    ASF_LOGI(TAG, 2001, asf::logger::Criticality::LOW, "ADC wrapper destroyed");
}

bool Adc::initialize()
{
    ASF_LOGI(TAG, 2002, asf::logger::Criticality::LOW, "ADC initialized successfully");
    return true;
}

bool Adc::initializeUnit(AdcUnit unit, AdcBitwidth bitwidth)
{
    uint8_t unitIdx = getUnitIndex(unit);
    
    if (m_unitInitialized_[unitIdx]) {
        ASF_LOGW(TAG, 2003, asf::logger::Criticality::MEDIUM, "ADC unit %d already initialized", unitIdx + 1);
        return true;
    }

    // Configure ADC unit
    adc_oneshot_unit_init_cfg_t initConfig = {};
    initConfig.unit_id = convertUnit(unit);
    
    esp_err_t ret = adc_oneshot_new_unit(&initConfig, &m_adcHandle_[unitIdx]);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2004, asf::logger::Criticality::HIGH, "Failed to initialize ADC unit %d: %s", unitIdx + 1, esp_err_to_name(ret));
        return false;
    }

    m_unitInitialized_[unitIdx] = true;
    m_unitBitwidth_[unitIdx] = bitwidth;
    ASF_LOGI(TAG, 2005, asf::logger::Criticality::LOW, "ADC unit %d initialized successfully", unitIdx + 1);
    return true;
}

bool Adc::deinitializeUnit(AdcUnit unit)
{
    uint8_t unitIdx = getUnitIndex(unit);
    
    if (!m_unitInitialized_[unitIdx]) {
        ASF_LOGW(TAG, 2006, asf::logger::Criticality::MEDIUM, "ADC unit %d not initialized", unitIdx + 1);
        return true;
    }

    // Deinitialize all calibrations for this unit
    for (int ch = 0; ch < 10; ch++) {
        if (m_channelConfigured_[unitIdx][ch]) {
            deinitializeCalibration(unit, static_cast<AdcChannel>(ch));
            m_channelConfigured_[unitIdx][ch] = false;
        }
    }

    esp_err_t ret = adc_oneshot_del_unit(m_adcHandle_[unitIdx]);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2007, asf::logger::Criticality::HIGH, "Failed to deinitialize ADC unit %d: %s", unitIdx + 1, esp_err_to_name(ret));
        return false;
    }

    m_adcHandle_[unitIdx] = nullptr;
    m_unitInitialized_[unitIdx] = false;
    ASF_LOGI(TAG, 2008, asf::logger::Criticality::LOW, "ADC unit %d deinitialized", unitIdx + 1);
    return true;
}

bool Adc::configureChannel(const AdcChannelConfig& config)
{
    uint8_t unitIdx = getUnitIndex(config.unit);
    uint8_t channelIdx = getChannelIndex(config.channel);
    
    if (!m_unitInitialized_[unitIdx]) {
        ASF_LOGE(TAG, 2009, asf::logger::Criticality::HIGH, "ADC unit %d not initialized", unitIdx + 1);
        return false;
    }

    // Configure channel
    adc_oneshot_chan_cfg_t chanConfig = {};
    chanConfig.atten = convertAttenuation(config.atten);
    chanConfig.bitwidth = convertBitwidth(config.bitwidth);
    
    esp_err_t ret = adc_oneshot_config_channel(m_adcHandle_[unitIdx], 
                                               convertChannel(config.channel), &chanConfig);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2010, asf::logger::Criticality::HIGH, "Failed to configure ADC channel %d on unit %d: %s", 
                 channelIdx, unitIdx + 1, esp_err_to_name(ret));
        return false;
    }

    // Initialize calibration
    if (!initializeCalibration(config.unit, config.channel, config.atten)) {
        ASF_LOGW(TAG, 2011, asf::logger::Criticality::MEDIUM, "Failed to initialize calibration for channel %d on unit %d", 
                 channelIdx, unitIdx + 1);
    }

    m_channelConfigured_[unitIdx][channelIdx] = true;
    ASF_LOGI(TAG, 2012, asf::logger::Criticality::LOW, "ADC channel %d configured on unit %d", channelIdx, unitIdx + 1);
    return true;
}

int32_t Adc::readVoltage(AdcUnit unit, AdcChannel channel)
{
    uint8_t unitIdx = getUnitIndex(unit);
    uint8_t channelIdx = getChannelIndex(channel);
    
    if (!m_unitInitialized_[unitIdx]) {
        ASF_LOGE(TAG, 2013, asf::logger::Criticality::HIGH, "ADC unit %d not initialized", unitIdx + 1);
        return -1;
    }
    
    if (!m_channelConfigured_[unitIdx][channelIdx]) {
        ASF_LOGE(TAG, 2014, asf::logger::Criticality::HIGH, "ADC channel %d not configured on unit %d", channelIdx, unitIdx + 1);
        return -1;
    }

    // Read raw value
    int rawValue = 0;
    esp_err_t ret = adc_oneshot_read(m_adcHandle_[unitIdx], convertChannel(channel), &rawValue);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2015, asf::logger::Criticality::HIGH, "Failed to read ADC channel %d on unit %d: %s", 
                 channelIdx, unitIdx + 1, esp_err_to_name(ret));
        return -1;
    }

    // Convert to voltage if calibration is available
    if (m_caliHandle_[unitIdx][channelIdx] != nullptr) {
        int voltage = 0;
        ret = adc_cali_raw_to_voltage(m_caliHandle_[unitIdx][channelIdx], rawValue, &voltage);
        if (ret == ESP_OK) {
            return voltage;
        } else {
            ASF_LOGW(TAG, 2016, asf::logger::Criticality::MEDIUM, "Failed to convert raw to voltage: %s", esp_err_to_name(ret));
        }
    }

    // Fallback: estimate voltage based on attenuation and raw value
    uint32_t maxRaw = getMaxRawValue(m_unitBitwidth_[unitIdx]);
    return (rawValue * 3300) / maxRaw;  // Rough estimation
}

int32_t Adc::readRaw(AdcUnit unit, AdcChannel channel)
{
    uint8_t unitIdx = getUnitIndex(unit);
    uint8_t channelIdx = getChannelIndex(channel);
    
    if (!m_unitInitialized_[unitIdx]) {
        ASF_LOGE(TAG, 2017, asf::logger::Criticality::HIGH, "ADC unit %d not initialized", unitIdx + 1);
        return -1;
    }
    
    if (!m_channelConfigured_[unitIdx][channelIdx]) {
        ASF_LOGE(TAG, 2018, asf::logger::Criticality::HIGH, "ADC channel %d not configured on unit %d", channelIdx, unitIdx + 1);
        return -1;
    }

    int rawValue = 0;
    esp_err_t ret = adc_oneshot_read(m_adcHandle_[unitIdx], convertChannel(channel), &rawValue);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2019, asf::logger::Criticality::HIGH, "Failed to read ADC channel %d on unit %d: %s", 
                 channelIdx, unitIdx + 1, esp_err_to_name(ret));
        return -1;
    }

    return rawValue;
}

int32_t Adc::readVoltageAverage(AdcUnit unit, AdcChannel channel, uint32_t samples)
{
    if (samples == 0) {
        ASF_LOGE(TAG, 2020, asf::logger::Criticality::HIGH, "Invalid sample count");
        return -1;
    }

    int64_t sum = 0;
    uint32_t validSamples = 0;

    for (uint32_t i = 0; i < samples; i++) {
        int32_t voltage = readVoltage(unit, channel);
        if (voltage >= 0) {
            sum += voltage;
            validSamples++;
        }
    }

    if (validSamples == 0) {
        ASF_LOGE(TAG, 2021, asf::logger::Criticality::HIGH, "No valid samples obtained");
        return -1;
    }

    return static_cast<int32_t>(sum / validSamples);
}

int32_t Adc::readRawAverage(AdcUnit unit, AdcChannel channel, uint32_t samples)
{
    if (samples == 0) {
        ASF_LOGE(TAG, 2022, asf::logger::Criticality::HIGH, "Invalid sample count");
        return -1;
    }

    int64_t sum = 0;
    uint32_t validSamples = 0;

    for (uint32_t i = 0; i < samples; i++) {
        int32_t raw = readRaw(unit, channel);
        if (raw >= 0) {
            sum += raw;
            validSamples++;
        }
    }

    if (validSamples == 0) {
        ASF_LOGE(TAG, 2023, asf::logger::Criticality::HIGH, "No valid samples obtained");
        return -1;
    }

    return static_cast<int32_t>(sum / validSamples);
}

bool Adc::isUnitInitialized(AdcUnit unit) const
{
    uint8_t unitIdx = getUnitIndex(unit);
    return m_unitInitialized_[unitIdx];
}

bool Adc::isChannelConfigured(AdcUnit unit, AdcChannel channel) const
{
    uint8_t unitIdx = getUnitIndex(unit);
    uint8_t channelIdx = getChannelIndex(channel);
    return m_channelConfigured_[unitIdx][channelIdx];
}

uint32_t Adc::getMaxRawValue(AdcBitwidth bitwidth)
{
    switch (bitwidth) {
        case AdcBitwidth::WIDTH_9BIT:
            return 511;
        case AdcBitwidth::WIDTH_10BIT:
            return 1023;
        case AdcBitwidth::WIDTH_11BIT:
            return 2047;
        case AdcBitwidth::WIDTH_12BIT:
            return 4095;
        case AdcBitwidth::WIDTH_13BIT:
            return 8191;
        default:
            return 4095;
    }
}

AdcChannelConfig Adc::getDefaultChannelConfig()
{
    AdcChannelConfig config = {};
    config.unit = AdcUnit::UNIT_1;
    config.channel = AdcChannel::CHANNEL_0;
    config.atten = AdcAttenuation::ATTEN_11dB;
    config.bitwidth = AdcBitwidth::WIDTH_12BIT;
    return config;
}

adc_unit_t Adc::convertUnit(AdcUnit unit)
{
    return static_cast<adc_unit_t>(unit);
}

adc_channel_t Adc::convertChannel(AdcChannel channel)
{
    return static_cast<adc_channel_t>(channel);
}

adc_atten_t Adc::convertAttenuation(AdcAttenuation atten)
{
    return static_cast<adc_atten_t>(atten);
}

adc_bitwidth_t Adc::convertBitwidth(AdcBitwidth bitwidth)
{
    return static_cast<adc_bitwidth_t>(bitwidth);
}

bool Adc::initializeCalibration(AdcUnit unit, AdcChannel channel, AdcAttenuation atten)
{
    uint8_t unitIdx = getUnitIndex(unit);
    uint8_t channelIdx = getChannelIndex(channel);

    adc_cali_line_fitting_config_t caliConfig = {};
    caliConfig.unit_id = convertUnit(unit);
    caliConfig.atten = convertAttenuation(atten);
    caliConfig.bitwidth = convertBitwidth(m_unitBitwidth_[unitIdx]);
#if CONFIG_IDF_TARGET_ESP32
    caliConfig.default_vref = 1100; // Default Vref for ESP32
#endif

    esp_err_t ret = adc_cali_create_scheme_line_fitting(&caliConfig, &m_caliHandle_[unitIdx][channelIdx]);
    if (ret == ESP_OK) {
        ASF_LOGI(TAG, 2024, asf::logger::Criticality::LOW, "Calibration initialized for unit %d channel %d", unitIdx + 1, channelIdx);
        return true;
    } else {
        ASF_LOGW(TAG, 2025, asf::logger::Criticality::MEDIUM, "Failed to initialize calibration for unit %d channel %d: %s", 
                 unitIdx + 1, channelIdx, esp_err_to_name(ret));
        m_caliHandle_[unitIdx][channelIdx] = nullptr;
        return false;
    }
}

bool Adc::deinitializeCalibration(AdcUnit unit, AdcChannel channel)
{
    uint8_t unitIdx = getUnitIndex(unit);
    uint8_t channelIdx = getChannelIndex(channel);

    if (m_caliHandle_[unitIdx][channelIdx] != nullptr) {
        esp_err_t ret = adc_cali_delete_scheme_line_fitting(m_caliHandle_[unitIdx][channelIdx]);
        if (ret != ESP_OK) {
            ASF_LOGW(TAG, 2026, asf::logger::Criticality::MEDIUM, "Failed to deinitialize calibration: %s", esp_err_to_name(ret));
            return false;
        }
        m_caliHandle_[unitIdx][channelIdx] = nullptr;
    }
    
    return true;
}

uint8_t Adc::getUnitIndex(AdcUnit unit) const
{
    return static_cast<uint8_t>(unit) - 1;
}

uint8_t Adc::getChannelIndex(AdcChannel channel) const
{
    return static_cast<uint8_t>(channel);
}
