/**
 * @file ads1015_driver.c
 * @brief Implementation of the ADS1015 I2C ADC driver.
 *
 * This driver provides functions to configure and read from the ADS1015
 * 12-bit, 128 SPS I2C ADC. The device supports up to four single-ended
 * inputs or two differential inputs.
 */

#include "ads1015_driver.h"

ADS1015_I2C i2c;
float ph_dut_samples[ADS1015_PH_AVERAGES]={0};

// Write the register
static void writeRegister(ADS1015_I2C *i2c, uint8_t reg, uint16_t value) {
	uint8_t pData[3] = { reg, (uint8_t) (value >> 8), (uint8_t) (value & 0xFF) };
	HAL_I2C_Master_Transmit(i2c->hi2c, i2c->m_i2cAddress, pData, 3, 10);
}

// Read the register
static uint16_t readRegister(ADS1015_I2C *i2c, uint8_t reg) {
	HAL_I2C_Master_Transmit(i2c->hi2c, i2c->m_i2cAddress, &reg, 1, 10);
	uint8_t pData[2] = { 0, 0 };
	HAL_I2C_Master_Receive(i2c->hi2c, i2c->m_i2cAddress, pData, 2, 10);
	return ((pData[0] << 8) | pData[1]);
}

// Check if we have correct connection.
static void ADSbegin(ADS1015_I2C *i2c) {
	if (HAL_I2C_IsDeviceReady(i2c->hi2c, i2c->m_i2cAddress, 10, 10) != HAL_OK)
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // This MUST have GPIO PA5 ready to use - ERROR I2C - Wrong address

}

// Declare an ADS1015 structure
void ADS1015(ADS1015_I2C *i2c, I2C_HandleTypeDef *hi2c, uint8_t i2cAddress) {
	i2c->hi2c = hi2c;
	i2c->m_i2cAddress = i2cAddress << 1; //  It's Important to shift the address << 1
	i2c->m_conversionDelay = ADS1015_CONVERSIONDELAY;
	i2c->m_bitShift = 4;
	i2c->m_gain = GAIN_TWOTHIRDS; /* +/- 6.144V range (limited to VDD +0.3V max!) */
	//ADSbegin(i2c); //Ready is not used
}

/*
 * // The ADC input range (or gain) can be changed via the following
 // functions, but be careful never to exceed VDD +0.3V max, or to
 // exceed the upper and lower limits if you adjust the input range!
 // Setting these values incorrectly may destroy your ADC!
 //                                                                ADS1015  ADS1115
 //                                                                -------  -------
 // ADSsetGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 3mV      0.1875mV (default)
 // ADSsetGain(GAIN_ONE);        // 1x gain   +/- 4.096V  1 bit = 2mV      0.125mV
 // ADSsetGain(GAIN_TWO);        // 2x gain   +/- 2.048V  1 bit = 1mV      0.0625mV
 // ADSsetGain(GAIN_FOUR);       // 4x gain   +/- 1.024V  1 bit = 0.5mV    0.03125mV
 // ADSsetGain(GAIN_EIGHT);      // 8x gain   +/- 0.512V  1 bit = 0.25mV   0.015625mV
 // ADSsetGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV
 */
void ADSsetGain(ADS1015_I2C *i2c, adsGain_t gain) {
	i2c->m_gain = gain;
}

// Get the gain
adsGain_t ADSgetGain(ADS1015_I2C *i2c) {
	return i2c->m_gain;
}

// Gets a single-ended ADC reading from the specified channel
uint16_t ADSreadADC_SingleEnded(ADS1015_I2C *i2c, uint8_t channel) {
	if (channel > 3) {
		return 0;
	}

	// Start with default values
	uint16_t config =
	ADS1015_REG_CONFIG_CQUE_NONE 			|   	// Disable the comparator (default val)
			ADS1015_REG_CONFIG_CLAT_NONLAT 	|  	// Non-latching (default val)
			ADS1015_REG_CONFIG_CPOL_ACTVLOW | 	// Alert/Rdy active low   (default val)
			ADS1015_REG_CONFIG_CMODE_TRAD 	| 	// Traditional comparator (default val)
			ADS1015_REG_CONFIG_DR_1600SPS 	| 	// 1600 samples per second (default)
			ADS1015_REG_CONFIG_MODE_SINGLE;   	// Single-shot mode (default)

	// Set PGA/voltage range
	config |= i2c->m_gain;

	// Set single-ended input channel
	switch (channel) {
	case (0):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_0;
		break;
	case (1):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_1;
		break;
	case (2):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_2;
		break;
	case (3):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_3;
		break;
	}

	// Set 'start single-conversion' bit
	config |= ADS1015_REG_CONFIG_OS_SINGLE;

	// Write config register to the ADC
	writeRegister(i2c, ADS1015_REG_POINTER_CONFIG, config);

	// Wait for the conversion to complete
	HAL_Delay(i2c->m_conversionDelay);

	// Read the conversion results
	// Shift 12-bit results right 4 bits for the ADS1015
	return readRegister(i2c, ADS1015_REG_POINTER_CONVERT) >> i2c->m_bitShift;
}

/*
 * Reads the conversion results, measuring the voltage
 * difference between the P (AIN0) and N (AIN1) input.  Generates
 * a signed value since the difference can be either positive or negative.
 */
int16_t ADSreadADC_Differential_0_1(ADS1015_I2C *i2c) {
	// Start with default values
	uint16_t config =
	ADS1015_REG_CONFIG_CQUE_NONE 	|   // Disable the comparator (default val)
	ADS1015_REG_CONFIG_CLAT_NONLAT 	|  	// Non-latching (default val)
	ADS1015_REG_CONFIG_CPOL_ACTVLOW | 	// Alert/Rdy active low   (default val)
	ADS1015_REG_CONFIG_CMODE_TRAD 	| 	// Traditional comparator (default val)
	ADS1015_REG_CONFIG_DR_128SPS 	| 	// 128 samples per second (default)
	ADS1015_REG_CONFIG_MODE_SINGLE;   	// Single-shot mode (default)

	// Set PGA/voltage range
	config |= i2c->m_gain;

	// Set channels
	config |= ADS1015_REG_CONFIG_MUX_DIFF_0_1; // AIN0 = P, AIN1 = N

	// Set 'start single-conversion' bit
	config |= ADS1015_REG_CONFIG_OS_SINGLE;

	// Write config register to the ADC
	writeRegister(i2c, ADS1015_REG_POINTER_CONFIG, config);

	// Wait for the conversion to complete
	HAL_Delay(i2c->m_conversionDelay);

	// Read the conversion results
	uint16_t res = readRegister(i2c, ADS1015_REG_POINTER_CONVERT) >> i2c->m_bitShift;
	if (i2c->m_bitShift == 0) {
		return (int16_t) res;
	} else {
		// Shift 12-bit results right 4 bits for the ADS1015,
		// making sure we keep the sign bit intact
		if (res > 0x07FF) {
			// negative number - extend the sign to 16th bit
			res |= 0xF000;
		}
		return (int16_t) res;
	}
}

/*
 * Reads the conversion results, measuring the voltage
 * difference between the P (AIN2) and N (AIN3) input.  Generates
 * a signed value since the difference can be either positive or negative.
 */
int16_t ADSreadADC_Differential_2_3(ADS1015_I2C *i2c) {
	// Start with default values
	uint16_t config =
	ADS1015_REG_CONFIG_CQUE_NONE 	|   	// Disable the comparator (default val)
	ADS1015_REG_CONFIG_CLAT_NONLAT 	|  	// Non-latching (default val)
	ADS1015_REG_CONFIG_CPOL_ACTVLOW | 	// Alert/Rdy active low   (default val)
	ADS1015_REG_CONFIG_CMODE_TRAD 	| 	// Traditional comparator (default val)
	ADS1015_REG_CONFIG_DR_1600SPS 	| 	// 1600 samples per second (default)
	ADS1015_REG_CONFIG_MODE_SINGLE;   	// Single-shot mode (default)

	// Set PGA/voltage range
	config |= i2c->m_gain;

	// Set channels
	config |= ADS1015_REG_CONFIG_MUX_DIFF_2_3; // AIN2 = P, AIN3 = N

	// Set 'start single-conversion' bit
	config |= ADS1015_REG_CONFIG_OS_SINGLE;

	// Write config register to the ADC
	writeRegister(i2c, ADS1015_REG_POINTER_CONFIG, config);

	// Wait for the conversion to complete
	HAL_Delay(i2c->m_conversionDelay);

	// Read the conversion results
	uint16_t res = readRegister(i2c, ADS1015_REG_POINTER_CONVERT) >> i2c->m_bitShift;
	if (i2c->m_bitShift == 0) {
		return (int16_t) res;
	} else {
		// Shift 12-bit results right 4 bits for the ADS1015,
		// making sure we keep the sign bit intact
		if (res > 0x07FF) {
			// negative number - extend the sign to 16th bit
			res |= 0xF000;
		}
		return (int16_t) res;
	}
}

/*
 * Sets up the comparator to operate in basic mode, causing the
 * ALERT/RDY pin to assert (go from high to low) when the ADC
 * value exceeds the specified threshold.
 * This will also set the ADC in continuous conversion mode.
 */
void ADSstartComparator_SingleEnded(ADS1015_I2C *i2c, uint8_t channel, int16_t threshold) {
	// Start with default values
	uint16_t config =
	ADS1015_REG_CONFIG_CQUE_1CONV 	|   	// Comparator enabled and asserts on 1 match
	ADS1015_REG_CONFIG_CLAT_LATCH 	|   	// Latching mode
	ADS1015_REG_CONFIG_CPOL_ACTVLOW | 	// Alert/Rdy active low   (default val)
	ADS1015_REG_CONFIG_CMODE_TRAD 	| 	// Traditional comparator (default val)
	ADS1015_REG_CONFIG_DR_1600SPS 	|	 // 1600 samples per second (default)
	ADS1015_REG_CONFIG_MODE_CONTIN 	|  	// Continuous conversion mode
	ADS1015_REG_CONFIG_MODE_CONTIN;   	// Continuous conversion mode

	// Set PGA/voltage range
	config |= i2c->m_gain;

	// Set single-ended input channel
	switch (channel) {
	case (0):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_0;
		break;
	case (1):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_1;
		break;
	case (2):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_2;
		break;
	case (3):
		config |= ADS1015_REG_CONFIG_MUX_SINGLE_3;
		break;
	}

	// Set the high threshold register
	// Shift 12-bit results left 4 bits for the ADS1015
	writeRegister(i2c, ADS1015_REG_POINTER_HITHRESH, threshold << i2c->m_bitShift);

	// Write config register to the ADC
	writeRegister(i2c, ADS1015_REG_POINTER_CONFIG, config);
}

/*
 * In order to clear the comparator, we need to read the conversion results.
 * This function reads the last conversion results without changing the config value.
 */
int16_t ADSgetLastConversionResults(ADS1015_I2C *i2c) {
	// Wait for the conversion to complete
	HAL_Delay(i2c->m_conversionDelay);

	// Read the conversion results
	uint16_t res = readRegister(i2c, ADS1015_REG_POINTER_CONVERT) >> i2c->m_bitShift;
	if (i2c->m_bitShift == 0) {
		return (int16_t) res;
	} else {
		// Shift 12-bit results right 4 bits for the ADS1015,
		// making sure we keep the sign bit intact
		if (res > 0x07FF) {
			// negative number - extend the sign to 16th bit
			res |= 0xF000;
		}
		return (int16_t) res;
	}
}

float ADSCalculate_ph_mV(void) // pH in mV
{
	float ph_dut_sum;
	uint8_t i;

	for (i = (ADS1015_PH_AVERAGES-1); i > 0; i--)
		ph_dut_samples[i] = ph_dut_samples[i-1];

	ph_dut_samples[0] = (((float)(ADSreadADC_Differential_0_1(&i2c)) * ADS1015_ADC_VREF) / ADS1015_ADC_MAX); 	// 1. Converter leitura bruta do ADC para tensão real (mV)

	for (i = 0, ph_dut_sum=0; i < ADS1015_PH_AVERAGES; i++)
		ph_dut_sum += ph_dut_samples[i];

    return (ADSRound_Float_Precision((ph_dut_sum/ADS1015_PH_AVERAGES),2));
}





/**
 * @brief Calcula o valor de pH compensado pela temperatura.
 *
 * @param adc_raw       Valor bruto lido do ADC (0 a 4095).
 * @param temp_dut      Temperatura atual medida em Celsius.
 *
 * @return float        Valor de pH calculado (0.0 a 14.0).
 */

float ADSCalculate_ph_Compensated(float temp_dut)
{

	    float dut_mV = ADSCalculate_ph_mV();

	    float t_cal_k = flash_data.ph_temperature + 273.15f;
	    float t_dut_k = temp_dut + 273.15f;

	    float slope_dut = flash_data.ph_slope_mV * (t_dut_k / t_cal_k);

	    float ph_result = (flash_data.ph7_real + ((dut_mV - flash_data.ph7_mV) / slope_dut));

	    return (ADSRound_Float_Precision(ph_result,2));

}


/**
 * @brief Calculates the pH value without temperature compensation.
 *
 * This version ignores the current temperature (temp_dut) and assumes
 * the electrode slope remains constant as calibrated.
 *
 * @param temp_dut Unused in this non-compensated version.
 * @return float Calculated pH value [0.0, 14.0]. Returns 0.0 if calibration error detected.
 */
float ADSCalculate_ph_Uncompensated(void)
{
    /*
     * 1. Convert raw ADC reading to actual voltage (Volts).
     * Note: The subtraction of 2048 assumes a differential configuration
     * where the midpoint is at the mid-scale of the ADS1015.
     */
    float v_measured = ((float)(2048 - ADSreadADC_Differential_0_1(&i2c)) * ADS1015_ADC_VREF) / ADS1015_ADC_MAX;

    /*
     * 2. Calculate the original Slope (measured during calibration).
     * PH_DELTA_CALIB is a constant representing the pH difference between
     * calibration points (e.g., |7.0 - 4.0| = 3.0).
     */
    float delta_v_calib = flash_data.ph7_mV - flash_data.ph4_mV;

    /* Safety check: Prevent division by zero if calibration data is invalid */
    if (delta_v_calib == 0.0f)
    {
        return 0.0f;
    }

    /* Slope = Delta pH / Delta Voltage */
    float slope_at_calib = PH_DELTA_CALIB / delta_v_calib;

    /*
     * 3. Final pH Calculation (Non-compensated)
     * Formula: pH = pH_ref + (V_measured - V_ref_7) * Slope
     * Where pH_ref is 7.0.
     */
    float ph_result = 7.0f + ((v_measured - flash_data.ph7_mV) * slope_at_calib);

    /* 4. Clamping (Ensure physical limits of the pH scale) */
    if (ph_result < 0.0f)
    {
        ph_result = 0.0f;
    }
    else if (ph_result > 14.0f)
    {
        ph_result = 14.0f;
    }

    return ph_result;
}

/**
 * @brief Performs linear interpolation for a given temperature within the LUT.
 *
 * @param temp_target   The target temperature to interpolate for.
 * @param row_index     Index of the buffer row (1 for pH4, 2 for pH7).
 * @return float        Interpolated value from the matrix.
 */
float ADSinterpolate_ph(float temp_target, uint8_t row_index)
{

	/*  Matrix with {Temperature, Ph 4 Buffer, Ph 7 Buffer} */
	const float lut_matrix_buffer_ph[ADS1015_INTERPOLATION_ROWS][ADS1015_PH_TEMP_INTER_POINTS] = {
			{0.0f, 5.0f, 10.0f, 15.0f, 20.0f, 25.0f, 30.0f, 35.0f, 40.0f, 45.0f, 50.0f, 55.0f}, 	// Temperature
			{4.01f, 4.00f, 4.00f, 4.00f, 4.00f, 4.01f, 4.01f, 4.02f, 4.03f, 4.05f, 4.06f, 4.10f}, 	// Buffer ph 4
			{7.12f, 7.07f, 7.05f, 7.03f, 7.01f, 7.00f, 6.98f, 6.97f, 6.96f, 6.96f, 6.96f, 6.96f},};	// Buffer ph 7
	uint8_t i = 0;

	/* Boundary Check: Clamp to first or last index if out of range */
	if (temp_target <= lut_matrix_buffer_ph[0][0])
		return lut_matrix_buffer_ph[row_index][0];
	if (temp_target >= lut_matrix_buffer_ph[0][ADS1015_PH_TEMP_INTER_POINTS-1])
		return lut_matrix_buffer_ph[row_index][ADS1015_PH_TEMP_INTER_POINTS-1];

	/* Find interpolation interval */
	while (i < (ADS1015_PH_TEMP_INTER_POINTS-1) && lut_matrix_buffer_ph[0][i+1] < temp_target)
	{
		i++;
	}

	float x0 = lut_matrix_buffer_ph[0][i];
	float x1 = lut_matrix_buffer_ph[0][i+1];
	float y0 = lut_matrix_buffer_ph[row_index][i];
	float y1 = lut_matrix_buffer_ph[row_index][i+1];

	/* Linear Interpolation: y = y0 + (x - x0) * ((y1 - y_0) / (x1 - x0)) */
	return y0 + (temp_target - x0) * ((y1 - y0) / (x1 - x0));
}

/**
 * @brief Reduz a precisão decimal de um float através de truncamento.
 *
 * @param valor O valor float original.
 * @param casas_decimais Quantidade de casas que devem permanecer após a vírgula.
 * @return float O valor com as casas decimais excedentes removidas.
 */
float ADSRound_Float_Precision(float value, uint8_t number_of_decimals)
{
    float multiply = 1.0f;

    for (uint8_t i = 0; i < number_of_decimals; i++)
    	multiply *= 10.0f;

    return roundf(value * multiply) / multiply;
}


