#include "stm32l1xx_hal.h"
#include "app/thermo/NTCtsensor.h"
#include "core/csect.h"
#include "core/config.h"
#include "core/config_pins.h"
#include "drivers/adc/adc.h"
#include "app/nfm/nfm_base.h"
#include "math.h"

// THERMO_SENSOR_KEEP_INITIALIZED
// Do not turn off (=1) the sensor periferal driver btw measures
#define NTC_THERMO_SENSOR_KEEP_INITIALIZED  1

// THERMO_SENSOR_AVG_WINDOW
// Digital exp-filter window size
#define NTC_THERMO_SENSOR_AVG_WINDOW        8   

// THERMO_SENSOR_INTERVAL
// Temperature measuring interval, ms
#define NTC_DETECTION_HIGH_LEVEL            3970 // (3.2v) Value that indicates existance of sensor on line
#define NTC_DETECTION_LOW_LEVEL             15   // (0.012v) Value that indicates existance of sensor on line
#define NTC_THERMO_SENSOR_INTERVAL          CONFIG_NTC_TSENSOR_INTERVAL
#define NTC_LOW_RESISTANCE                  1e3
#define NTC_HIGH_RESISTANCE                 35e4

#define NTC_DEFAULT_TEMP_COEFF              3988

static tNTCSensorTemp vNtcThermoSensor_AVGTemp = THERMO_SENSOR_INVALID_TEMP_VALUE;
static size_t vNtcThermoSensor_AVGPoints = 0;
static uint32_t vNtcThermoSensor_LastTick = 0;
static uint32_t vNtcThermoSensor_Interval = NTC_THERMO_SENSOR_INTERVAL;

static bool vNtcThermoSensor_bInitialized = false;

static bool NtcThermoSensor_Init();
static void NtcThermoSensor_UpdateAVGTemp( tNTCSensorTemp new_value ) ;   
static void NtcThermoSensor_ResetAvgTemp();
static bool NtcThermoSensor_Serve();
static bool NtcThermoSensor_GetReady();
static tNTCSensorTemp NtcThermoSensor_GetAvgTemp();
static void NtcThermoSensor_SetInterval(size_t ms);
static bool NtcThermoSensor_GetAdcValue( tNTCSensorTemp * temp );
static void NtcUpdateCurrentTempCoeff(uint16_t temp_coeff);

const sNtcThermoSensorHandle_t NtcThermoSensor = {
    .Init = NtcThermoSensor_Init,
    .GetReady = NtcThermoSensor_GetReady,
    .ResetTempAVG = NtcThermoSensor_ResetAvgTemp,
    .ServeSensor = NtcThermoSensor_Serve,
    .GetTempAVG = NtcThermoSensor_GetAvgTemp,
    .SetSensorInterval = NtcThermoSensor_SetInterval,
    .UpdateCurrentTempCoeff = NtcUpdateCurrentTempCoeff
};

static uint16_t B_coeff = 0;

static bool NtcThermoSensor_Init()
{
   GPIO_InitTypeDef GPIO_NTC = {0};
   GPIO_NTC.Pin = CONFIG_PIN__NTC_TSENS_PWR; 
   GPIO_NTC.Mode = GPIO_MODE_ANALOG;
   GPIO_NTC.Pull = GPIO_NOPULL;      
   HAL_GPIO_Init(CONFIG_PORT__NTC_TSENS_PWR, &GPIO_NTC);
   
   if(!NFMClass->methods.tempCoefficient.getTempCoeff(&B_coeff))
   {
       B_coeff = NTC_DEFAULT_TEMP_COEFF;
   }
   
   return true;
}

static bool NtcThermoSensor_GetReady()
{
   bool bReady = false;

   __DI__
         bReady =  ( vNtcThermoSensor_AVGPoints > 0 ) 
                && ( vNtcThermoSensor_AVGTemp != THERMO_SENSOR_INVALID_TEMP_VALUE );
   __EI__

  return bReady;
}

static void NtcThermoSensor_SetInterval(uint32_t ms)
{
    __DI__ vNtcThermoSensor_Interval = ms; __EI__ 
}

static void NtcThermoSensor_ResetAvgTemp()
{
    DI();
    vNtcThermoSensor_AVGTemp = THERMO_SENSOR_INVALID_TEMP_VALUE;
    vNtcThermoSensor_AVGPoints = 0;
    EI();
}

static tNTCSensorTemp NtcThermoSensor_GetAvgTemp()
{
    tNTCSensorTemp value;
    __DI__ value = vNtcThermoSensor_AVGTemp; __EI__
    return value;
}

static int NtcThermoSensor_GetTempFromVoltage( tADCValue_t * adc_code )
{
    uint16_t voltage = *adc_code * 3300 / 4095;
    
    float T1 = 298;
    double R1 = 10e3;
    
    double R2 = R1 * voltage / (3300 - voltage); // Resistance of NTC
    if(R2 <= NTC_LOW_RESISTANCE) R2 = NTC_LOW_RESISTANCE;
    if(R2 >= NTC_HIGH_RESISTANCE) R2 = NTC_HIGH_RESISTANCE;
    float b_value = ((log(R1)) - (log(R2))) / B_coeff;
    float temperature = (1 / ( (1 / T1) - b_value)) - 273;
    return (int)(temperature * 100); // in °C
}

static bool NtcThermoSensor_GetAdcValue( tNTCSensorTemp * temp )
{
    tADCValue_t value;
    if(ADC1Handle.Start(CONFIG_ADC_CHANNEL_NTC_TSENS))
    {
        if(ADC1Handle.Measure(100, &value))
        {
            if(value <= NTC_DETECTION_HIGH_LEVEL && value >= NTC_DETECTION_LOW_LEVEL)
            {
                *temp = NtcThermoSensor_GetTempFromVoltage(&value); //Debug
                return true;
            }
        }
    }
    return false;
}

static void NtcUpdateCurrentTempCoeff(uint16_t temp_coeff)
{
    B_coeff = temp_coeff;
}

static bool NtcThermoSensor_Serve()
{
    bool bServe = false;

    __DI__ bServe = (HAL_GetTick() - vNtcThermoSensor_LastTick > vNtcThermoSensor_Interval); __EI__

    if( bServe )
    {
        if( !vNtcThermoSensor_bInitialized )
        {
            vNtcThermoSensor_bInitialized = NtcThermoSensor_Init();
        }

        if( vNtcThermoSensor_bInitialized )
        {
            tNTCSensorTemp temp_value;
            if( NtcThermoSensor_GetAdcValue( &temp_value ) )
            {            
                NtcThermoSensor_UpdateAVGTemp( temp_value );
            }
            else
            {
                NtcThermoSensor_ResetAvgTemp();
            }
        }

        __DI__ vNtcThermoSensor_LastTick = HAL_GetTick(); __EI__
    }

    return bServe;
}

// ThermoSensor_UpdateAVGTemp
// Performs averaging with exponential digital filter
static void NtcThermoSensor_UpdateAVGTemp( tNTCSensorTemp new_value ) 
{
    DI();
    if( (THERMO_SENSOR_INVALID_TEMP_VALUE) == vNtcThermoSensor_AVGTemp )
    {
        vNtcThermoSensor_AVGTemp = new_value;
        vNtcThermoSensor_AVGPoints = 1;
    }
    else
    {
        vNtcThermoSensor_AVGTemp = ( new_value + (tNTCSensorTemp)( vNtcThermoSensor_AVGTemp * vNtcThermoSensor_AVGPoints ) ) / (tNTCSensorTemp)( vNtcThermoSensor_AVGPoints + 1 );

        if( vNtcThermoSensor_AVGPoints < (NTC_THERMO_SENSOR_AVG_WINDOW) )
            vNtcThermoSensor_AVGPoints++;
        
        // Now @tmp is 16-bit value: [BBBBBBBBBB000000]
        // So, lets perform averaging: 
        //           n * AVG[i-1] + tmp
        //  AVG[i] = -------------------  
        //              n+1
        // where
        //  @AVG[i-1] is averaged temperature before operation,
        //  @AVG[i] is averaged temperature after operation,
        //  @n is the filter window size (=24),
        //  @tmp is momentary measured temperature, [BBBBBBBBBB000000].
        //
        // So after averaging, bits 5..0 will be filled up.
        // Since the sensor has a 0.25*C accuracy, the @tmp should be divided
        // by 4 to get the value in degrees. But after averaging the whole accuracy
        // is changed due to the bits 5..0 are filled up. So, we need to divide
        // the value not 4, but 256 times to get value in degrees:
        //  /4  - sensor accuracy
        //  /64 - averaging accuracy (6 bits, 2^6=64)
        // So, 4*64 = /256 of degree.
    }
    EI();
    
}
