//-----------------------------------------------------------------------------
///
/// \brief  Just a simple ADC
///
///         When converting an extena pin the pin has to be configured as
///         "GPIO Analog" via biwak.
///
/// \date   20121125
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


//---Includes------------------------------------------------------------------


//---Own------------------------------

#include <biwak/adc.hpp>
#include <biwak/rcc.h>
#include <biwak/lltools.h>
#include <lepto/log.h>           // lHint << ...
//#include <stm/stm32f4xx.h>       // ADC_IRQHandler
//#include <stm/stm32f4xx_hal_rcc.h>       // RCC_APB2Periph_ADC1
#include <HALWrapper/stm32_rcc.h>
//#include <HALWrapper/stm32_dma.h>       // stm32_adc needs it
#include <HALWrapper/stm32_adc.h>
#include <biwak/stm32/adc.hpp>

#include <biwak/handler.h>       // eInterruptAdc0
#include <biwak/nvic.h>          //
#include <string.h>              // memset
#include <biwak/exception.h>


//---Implementation------------------------------------------------------------


/// \brief  Le contructeur
///
CAdc::CAdc( const SAdcConfig &configData )
   //:m_configData(configData)
   :currentChannel( eAdcChannelNone )
   //,m_adcValue( 0 )
{
   HAL_StatusTypeDef sta = HAL_OK;

   m_pHandler = new ADC_HandleTypeDef {
      .Instance { configData.instance },
      .Init  {
         #if ! defined( STM32F1 )
            .ClockPrescaler        = ADC_CLOCK_ASYNC_DIV1,
            .Resolution            = ADC_RESOLUTION_12B,
         #endif

         .DataAlign             = ADC_DATAALIGN_RIGHT,

         #if defined(STM32G4)
            .GainCompensation = 0,
         #endif
   
         // has to be disabled for normal 
         #if defined(STM32F4)
            .ScanConvMode          = ENABLE;                     /* Sequencer enabled (ADC conversion on several channels, successively, following settings below) */
         #elif defined(STM32L1) || defined(STM32F1) || defined(STM32F3) || defined(STM32L4) || defined(STM32G4)
            .ScanConvMode          = ADC_SCAN_DISABLE,            /* Sequencer enabled (ADC conversion on several channels, successively, following settings below) */
         #elif defined(STM32F4)
            .ScanConvMode          = ADC_SCAN_DISABLE,
         #else
            .ScanConvMode          = ADC_SCAN_ENABLE,
         #endif
   
         #if ! defined(STM32F1)
            .EOCSelection          = ADC_EOC_SINGLE_CONV,
         #endif
   
         #if ! ( defined(STM32F4) || defined(STM32F1 ) )
            .LowPowerAutoWait      = DISABLE,
         #endif // ? ! STM32F4

         #if ! ( defined(STM32F3) || defined(STM32F1 ) || defined(STM32G4 ) || defined(STM32L4 ) )
            .LowPowerAutoPowerOff      = DISABLE,
         #endif // ? ! STM32F3
       
         /* Continuous mode disabled to have only 1 rank converted at each conversion trig, and because discontinuous mode is enabled */
         .ContinuousConvMode    = ENABLE,

         #if defined(STM32F3) || defined(STM32F1) || defined(STM32G0) || defined(STM32G4) || defined(STM32L4)
            .NbrOfConversion       = 1,
         #endif

         /* Sequencer of regular group will convert the sequence in several sub-divided sequences */
         .DiscontinuousConvMode = DISABLE,

         #if defined(STM32F3) || defined(STM32F1) || defined(STM32G4) || defined(STM32L4)
            .NbrOfDiscConversion   = 1,
         #endif

         #if defined STM32L1
            /* Trig of conversion start done manually by software, without external event */
            .ExternalTrigConv      = ADC_SOFTWARE_START,
         #else
            /* Trig of conversion start done manually by software, without external event */
            .ExternalTrigConv      = ADC_SOFTWARE_START,
         #endif

         #if ! defined(STM32F1)
            .ExternalTrigConvEdge      = ADC_EXTERNALTRIGCONVEDGE_NONE,
            #if defined (STM32G4)
               .SamplingMode = 0,
            #endif // ? STM32G4
            .DMAContinuousRequests = DISABLE,
         #endif
   
         #if ! ( defined(STM32F4) || defined(STM32F1) )
         #if ! defined (STM32L1)
            .Overrun               = ADC_OVR_DATA_OVERWRITTEN,
         #endif
         #endif

         #if ! defined (STM32F3) && ! defined(STM32F1) && ! defined(STM32F4) \
            && ! defined (STM32L1) && ! defined(STM32L0) && ! defined (STM32L4) \
                && ! defined (STM32G4) && ! defined (STM32G0)
                     .SamplingTimeCommon    = ADC_SAMPLETIME_239CYCLES_5, // ADC_SAMPLETIME_28CYCLES_5
         #endif
   
         #if ! ( defined(STM32F0) || defined(STM32L0) || defined(STM32L1) \
                  || defined(STM32F3) || defined(STM32F1) || defined(STM32G0)\
                  || defined(STM32G4) || defined(STM32L4) )
            .NbrOfConversion       = 1,
            #if ! defined ( STM32G0 )
               .NbrOfDiscConversion   = 1,
            #endif
         #endif

         #if defined(STM32G0)
            .SamplingTimeCommon1 = 0,
            .SamplingTimeCommon2 = 0,
         #endif // ? STM32G0
      },
   }; // ADC_HandleTypeDef

   #if defined ( BIWAK_AUTOMATIC_RCC_ENABLE )
   rccAdcClockEnable(m_pHandler->Instance);
   #endif
   
   HAL_ADC_DeInit( m_pHandler );
   
   setNVInterrupt( configData.irqn, true, NVI_LOW_PRIORITY );
   
   if ( ( sta = HAL_ADC_Init( m_pHandler ) ) != HAL_OK)
   {
      /* ADC initialization error */
      exception( );
   }
   
   bDebugAssert ( sta == HAL_OK, HAL_ADC_GetError(m_pHandler) );

   return;
}


CAdc::~CAdc()
{

}


#if ! ( defined(STM32F0) || defined(STM32L0) || defined(STM32G0) )
int CAdc::initAdcInjection()
{

   //adcInternalHandle = &adcdev->ADCHandle;

    ADC_InjectionConfTypeDef iConfig;

    //iConfig.InjectedChannel      = ADC_CHANNEL_VREFINT;
    //iConfig.InjectedRank         = 1;
#if defined STM32L4 || defined STM32G4
    iConfig.InjectedSamplingTime = ADC_SAMPLETIME_247CYCLES_5;
#elif defined(STM32F1)
   iConfig.InjectedSamplingTime = ADC_SAMPLETIME_239CYCLES_5;
#elif defined(STM32L1)
    iConfig.InjectedSamplingTime = ADC_SAMPLETIME_384CYCLES;
#else
    iConfig.InjectedSamplingTime = ADC_SAMPLETIME_601CYCLES_5;
#endif

    iConfig.InjectedOffset       = 0;
    iConfig.InjectedNbrOfConversion = 2;
    iConfig.InjectedDiscontinuousConvMode = DISABLE;
    iConfig.AutoInjectedConv     = DISABLE;
    iConfig.ExternalTrigInjecConv = 0;     // Don't care
   #if !defined ( STM32F103x6 )
   iConfig.ExternalTrigInjecConvEdge = 0; // Don't care
   #endif

    //if (HAL_ADCEx_InjectedConfigChannel( m_pHandler, &iConfig) != HAL_OK) {
        /* Channel Configuration Error */
    //}

    //iConfig.InjectedChannel      = ADC_CHANNEL_TEMPSENSOR;
    //iConfig.InjectedRank         = 2;

    if (HAL_ADCEx_InjectedConfigChannel( m_pHandler, &iConfig) != HAL_OK) {
        /* Channel Configuration Error */
    }

    /*
    adcVREFINTCAL = *(uint16_t *)VREFINT_CAL_ADDR;
    adcTSCAL1 = *TEMPSENSOR_CAL1_ADDR;
    adcTSCAL2 = *TEMPSENSOR_CAL2_ADDR;
    adcTSSlopeK = (TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP) * 1000 / (adcTSCAL2 - adcTSCAL1);
    */
   return(0);
}
#endif
int CAdc::configChannel( EAdcChannel channel )
{
   HAL_StatusTypeDef sta = HAL_OK;

   /* Configuration of channel on ADCx regular group on sequencer rank 1 */
   /* Note: Considering IT occurring after each ADC conversion (IT by DMA end  */
   /*       of transfer), select sampling time and ADC clock with sufficient   */
   /*       duration to not create an overhead situation in IRQHandler.        */
   /* Note: Set long sampling time due to internal channels (VrefInt,          */
   /*       temperature sensor) constraints.                                   */
   /*       For example, sampling time of temperature sensor must be higher    */
   /*       than 17.1us. Refer to device datasheet for min/typ/max values.     */
   // sConfig.Channel      = ADC_CHANNEL_4;
   //sConfig.Channel      = ADC_CHANNEL_TEMPSENSOR;
   //memset( &sConfig, 0, sizeof(sConfig));
   
   ADC_ChannelConfTypeDef   sConfig {
         .Channel { channelEnumToDefine( channel ) },
            
      // Grep it: only STM32F0 has it at the moment
      #if defined(STM32F0) ||  defined(STM32L0)
         .Rank          { ADC_RANK_CHANNEL_NUMBER },            
      #elif defined(STM32F4)
         .Rank         = 1,
      #else
         .Rank         =  ADC_REGULAR_RANK_1,
      #endif
            
      #if defined (STM32F0)
         .SamplingTime    { ADC_SAMPLETIME_239CYCLES_5 },
      #elif defined (STM32F3)
         .SamplingTime   { ADC_SAMPLETIME_601CYCLES_5 },
      #elif defined (STM32F1)
         .SamplingTime    { ADC_SAMPLETIME_239CYCLES_5 },
         //sConfig.SamplingTime  { ADC_SAMPLETIME_55CYCLES_5 },
      #elif defined (STM32L4)
         .SamplingTime    { ADC_SAMPLETIME_247CYCLES_5 },
       #elif defined (STM32G0)
         .SamplingTime    { ADC_SAMPLETIME_160CYCLES_5 },
      #endif

      #if ! ( defined(STM32L1) || defined(STM32F1) || defined(STM32F0) \
               || defined(STM32L0) || defined(STM32G0) )
         #if ! defined(STM32F4)
            .SingleDiff { ADC_SINGLE_ENDED  },
            .OffsetNumber { ADC_OFFSET_NONE  },
         #endif

         .Offset { 0x0  },
      #endif

      #if defined (STM32F3)
            //SamplingTime { ADC_SAMPLETIME_601CYCLES_5 },
            //SamplingTime { ADC_SAMPLETIME_1CYCLE_5 },
            //SamplingTime { ADC_SAMPLETIME_181CYCLES_5 },
            //Offset { 0 },
      #endif
   };

   #if 0
   if (HAL_ADC_ConfigChannel(m_pHandler, &sConfig) != HAL_OK)
   {
     /* Channel Configuration Error */
     exception("Config Channel",0);
   }
   #endif

   if ( (sta=HAL_ADC_ConfigChannel(m_pHandler, &sConfig)) != HAL_OK )
   {
     /* Channel Configuration Error */
     exception();
   }
   return(sta);
}


// For information about the corresponding pins hav a look at the MCUs
// Datasheet, chapter " Pinouts and pin description"
int CAdc::switchChannel( EAdcChannel channel )
{
   int sta;

   if(currentChannel==channel)
      return(0);
   
   /*
   if( currentChannel != eAdcChannelNone )
   {
      exception();
   }
   */

   currentChannel=channel;
   sta=configChannel( currentChannel );

   return(sta);
}


// For information about the corresponding pins hav a look at the MCUs
// Datasheet, chapter " Pinouts and pin description"
void CAdc::addChannel( EAdcChannel channel )
{
   configChannel( channel );
   return;
}


extern "C" {
   void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *AdcHandle);
}


void CAdc::interrupt() /*[virtual]*/
{
   exception();

   #if 0
   HAL_ADC_IRQHandler( m_pHandler );
   m_adcValue = 0; // ADC_GetConversionValue( &adcData[eAdc1].handler );
   exception("I");

   HAL_ADC_Start( m_pHandler );
   #endif
}

/*
temperature_t CAdc::getTemperature()
{
   return(m_temperature);
}


int CAdc::getVRef()
{
   return(m_vRef);
}
*/

int CAdc::accuireAdcValue()
{
   int sta;
   int m_adcValue;
   //configChannel(eAdcChannel0);

   if ( (sta=HAL_ADC_Start(m_pHandler) ) == HAL_OK )
   {
      if ( (sta=HAL_ADC_PollForConversion(m_pHandler, 1000)) == HAL_OK )
      {
         m_adcValue = HAL_ADC_GetValue(m_pHandler);
      }
      else
      {
         lWarning( LDS("CNAQ %d", "Could not get ADC value; %d"), sta);
         m_adcValue=-1;
      }
   }
   else
   {
      lWarning( LDS("CNSA", "Could not start ADC; "), sta);
      m_adcValue=-1;
   }

   // A stop is not neccessary (does not hurt and does not return error)
   sta=HAL_ADC_Stop(m_pHandler);

   return(m_adcValue);
}

/*
int CAdc::getAdcValue()
{
   return(m_adcValue);
}
*/


// See Data sheet, "Temperature sensor characteristics"
// value checked in datasheet for: STM32F103xB; STM32F407
#if defined( STM32F407xx )
   #define TEMPSENS_V25_MV          ((int32_t)760)         /* Internal temperature sensor, parameter V25 (unit: mV). Refer to device datasheet for min/typ/max values. */
   #define TEMPSENS_AVGSLOPE_MVPC10 ((int32_t)25)         /* Internal temperature sensor, parameter Avg_Slope (unit: uV/DegCelsius). Refer to device datasheet for min/typ/max values. */
#elif defined(STM32F103xB) || defined(STM32F3) || defined(STM32L1)
   #define TEMPSENS_V25_MV          ((int32_t)1430)         /* Internal temperature sensor, parameter V25 (unit: mV). Refer to device datasheet for min/typ/max values. */
   #define TEMPSENS_AVGSLOPE_MVPC10 ((int32_t)4300)         /* Internal temperature sensor, parameter Avg_Slope (unit: uV/DegCelsius). Refer to device datasheet for min/typ/max values. */
#elif defined(STM32F072xB)
   //#define TEMPSENS_V30_MV          ((int32_t)1430)         /* Internal temperature sensor, parameter V25 (unit: mV). Refer to device datasheet for min/typ/max values. */
   //#define TEMPSENS_AVGSLOPE_MVPC10 ((int32_t)4300)         /* Internal temperature sensor, parameter Avg_Slope (unit: uV/DegCelsius). Refer to device datasheet for min/typ/max values. */
#endif

/*
int CAdc::updateTemperature()
{
   int tempRaw, vRefRaw;

   //switchChannel(eAdcChannelVRef);
   //vRefRaw=accuireAdcValue();
   vRefRaw=0;

   //switchChannel(eAdcChannelTemperature);
   //sleep(1000);
   tempRaw=accuireAdcValue();

   m_temperature=calculateTemperature(tempRaw, vRefRaw);

   return( m_temperature );
}
*/

int CAdc::calculateTemperature(int tempRaw, int vRefRaw) /* static */
{
   int temperature=0;
   //int Vdd=0;
   // The adjust factor is: VREFINT_CAL_ADDR / VRefRaw
   #define MU  1
   #if defined ( STM32F3 )

      #if 1
      // Is it really that complicated?
      //Vdd = ( 3300 * (*VREFINT_CAL_ADDR) ) / vRefRaw;
      //Vdd = 3300;
      //temperature = (( ((int32_t)tempRaw * Vdd) /3300)- (int32_t) *TEMPSENSOR_CAL1_ADDR );

      temperature = ( (int32_t)tempRaw - (int32_t) *TEMPSENSOR_CAL1_ADDR );
      temperature = temperature * (int32_t)(TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP);
      temperature = temperature * MU;
      temperature = temperature / (int32_t)(*TEMPSENSOR_CAL2_ADDR - *TEMPSENSOR_CAL1_ADDR );
      temperature = temperature + (TEMPSENSOR_CAL1_TEMP * MU);
      #endif

   #if 0
      // temperature = ( ( ( TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP ) * ( tempRaw - *TEMPSENSOR_CAL1_ADDR ) ) / ( *TEMPSENSOR_CAL2_ADDR - *TEMPSENSOR_CAL2_ADDR ) ) + TEMPSENSOR_CAL1_TEMP ;
      temperature = ( ( ( TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP ) * ( tempRaw - *TEMPSENSOR_CAL1_ADDR ) )
                      / ( *TEMPSENSOR_CAL2_ADDR - *TEMPSENSOR_CAL1_ADDR ) ) + TEMPSENSOR_CAL1_TEMP ;
   #endif

   #elif defined ( STM32F1 )
      temperature = 0;

   #endif

#if defined ( STM32F3 )
      int adcTSCAL1 = *TEMPSENSOR_CAL1_ADDR;
      int adcTSCAL2 = *TEMPSENSOR_CAL2_ADDR;
      int adcTSSlopeK = (TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP) * 1000 / (adcTSCAL2 - adcTSCAL1);
      temperature = ( tempRaw - *TEMPSENSOR_CAL1_ADDR ) * adcTSSlopeK;
      temperature += TEMPSENSOR_CAL1_TEMP*1000;

      printf("CAL1: %d / %d\n", (int)*TEMPSENSOR_CAL1_ADDR, (int)TEMPSENSOR_CAL1_TEMP);
      printf("CAL2: %d / %d\n", (int)*TEMPSENSOR_CAL2_ADDR, (int)TEMPSENSOR_CAL2_TEMP);
      printf("RAW: %d\n", tempRaw);
      printf("VREF: %d\n", (int)*VREFINT_CAL_ADDR);
#endif

      #if 0
      // see datasheet for position of the calibration values, this is for STM32F429
      const uint16_t* const ADC_TEMP_3V3_30C =  reinterpret_cast<uint16_t*>(0x1FFF7A2C);
      const uint16_t* const ADC_TEMP_3V3_110C =  reinterpret_cast<uint16_t*>(0x1FFF7A2E);
      const float CALIBRATION_REFERENCE_VOLTAGE = 3.3F;

      const float REFERENCE_VOLTAGE = 3.0F; // supplied with Vref+ or VDDA

      // scale constants to current reference voltage
      int adcCalTemp30C = static_cast<int>(*TEMPSENSOR_CAL1_ADDR); // * (REFERENCE_VOLTAGE/CALIBRATION_REFERENCE_VOLTAGE);
      int adcCalTemp110C = static_cast<int>(*TEMPSENSOR_CAL2_ADDR); // * (REFERENCE_VOLTAGE/CALIBRATION_REFERENCE_VOLTAGE);

      uint16_t adcTempValue = tempRaw;

      float temperature2 = (static_cast<float>(adcTempValue) - adcCalTemp30C)/(adcCalTemp110C - adcCalTemp30C) * (110.0F - 25.0F) + 25.0F;
      #endif
      //temperature = TEMPSENSOR_CAL_VREFANALOG;
//#endif
   return(temperature);
}

CAdc *interruptAdc1=nullptr;
CAdc *interruptAdc2=nullptr;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes"

#if defined(STM32F0) && 0
void ADC1_COMP_IRQHandler(void)
{
   callInterruptFix(interruptAdc1);
}
#endif


#if defined STM32F1 || defined STM32F3
IRQ_Funcion_imp( ADC1_2_IRQHandler )
{
   callInterrupt(interruptAdc1);
   callInterrupt(interruptAdc2);
}
#endif

#if defined STM32L1
IRQ_Funcion_imp( ADC1_IRQHandler )
{
   callInterrupt(interruptAdc1);
}
#endif

#pragma GCC diagnostic pop

#if 0
extern "C"
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	//exception("Foo", 0 );
	if (__HAL_ADC_GET_FLAG(hadc, ADC_FLAG_EOC))
		{
		_ADC_raw[adcIndex] = HAL_ADC_GetValue(hadc);
		lHint << "### Raw: " <<  _ADC_raw[adcIndex];
		adcIndex++;
		}

	if (__HAL_ADC_GET_FLAG(hadc, ADC_FLAG_EOS
								  ))
		{
		lHint << "### Index: " << eLogBaseDec << adcIndex ;
		lHint << "### Raw: " <<  _ADC_raw[adcIndex-1];
		adcIndex=0;

#ifdef STM32F0
		Vdd = 3300 * (*VREFINT_CAL_ADDR) / _ADC_raw[2];

		//temperature = ((int32_t)_ADC_raw[1] - (int32_t) *TEMP30_CAL_ADDR );
		//temperature = (((int32_t)_ADC_raw[1] * Vdd/3300)- (int32_t) *TEMP30_CAL_ADDR );
		temperature = ( (int32_t)_ADC_raw[0] - (int32_t) *TEMP30_CAL_ADDR );

		temperature = temperature * (int32_t)(110 - 30);
		temperature = temperature / (int32_t)(*TEMP110_CAL_ADDR - *TEMP30_CAL_ADDR);
		temperature = temperature + 30;
		lHint << "### Temp: " << eLogBaseDec << temperature ;


		Vin = Vdd*_ADC_raw[0]/4095;
#else
		temperature=0;
#endif
		memset(_ADC_raw, 0, 10);
		}

}
#endif

//---fin-----------------------------------------------------------------------
