#include "drivers/adc/adc.h"
#include "stm32l1xx_hal.h"
#include "stm32l1xx_hal_adc.h"
#include "stm32l1xx_hal_adc_ex.h"
#include "core/csect.h"
#include "core/config_pins.h"

 static bool ADC_Init( eADC_Resolution_t resolution );
 static bool ADC_Start( uint32_t chid );
 static bool ADC_Measure( uint32_t timeout, tADCValue_t * pResult );
 static void ADC_Stop( uint32_t chid );
 static uint32_t ADC_GetVoltage(tADCValue_t * pResult);
 static void ADC_DeInit();
 
 static tADCValue_t ADCResult = 0;

 ADC_HandleTypeDef Adc1Handle;

 const ADC_Handle_t ADC1Handle = {

    ADC_Init,
    ADC_Start,
    ADC_Measure,
    ADC_Stop,
    ADC_GetVoltage,
    ADC_DeInit,
 };
 
 static bool ADC_Init( eADC_Resolution_t resolution )
 {
   DI();

    /* Configure the ADC1 IN0 for analog input */
   __HAL_RCC_HSI_ENABLE();
   __HAL_RCC_ADC1_CLK_ENABLE();
   
   GPIO_InitTypeDef GPIO_InitStruct = {0};
   GPIO_InitStruct.Pin = GPIO_PIN_1;
   GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
   GPIO_InitStruct.Pull = GPIO_NOPULL;
   HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
   /* Configure the ADC peripheral */
   Adc1Handle.Instance = ADC1;
   
   Adc1Handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;

   switch( resolution )
   {
      case eADCRes_12bit: Adc1Handle.Init.Resolution = ADC_RESOLUTION_12B; break;
      case eADCRes_10bit: Adc1Handle.Init.Resolution = ADC_RESOLUTION_10B; break;
      case eADCRes_8bit:  Adc1Handle.Init.Resolution = ADC_RESOLUTION_8B;  break;
      case eADCRes_6bit:
      default:
                          Adc1Handle.Init.Resolution = ADC_RESOLUTION_6B;  break;
   }

   Adc1Handle.Init.ScanConvMode          = DISABLE;                       
   Adc1Handle.Init.ContinuousConvMode    = DISABLE;                        
   Adc1Handle.Init.DiscontinuousConvMode = DISABLE;                       
   Adc1Handle.Init.NbrOfDiscConversion   = 0;
   Adc1Handle.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_NONE;
   Adc1Handle.Init.ExternalTrigConv      = ADC_SOFTWARE_START;        
   Adc1Handle.Init.DataAlign             = ADC_DATAALIGN_RIGHT;
   Adc1Handle.Init.NbrOfConversion       = 1;
   Adc1Handle.Init.DMAContinuousRequests = DISABLE;
   Adc1Handle.Init.EOCSelection          = DISABLE;
   Adc1Handle.Init.LowPowerAutoPowerOff  = ADC_AUTOPOWEROFF_DISABLE;
     
   bool bRet = ( HAL_OK == HAL_ADC_Init(&Adc1Handle) );   
   
   //HAL_NVIC_SetPriority( ADC1_IRQn, ADC1_INT_PRIORITY, 0);
   //HAL_NVIC_EnableIRQ( ADC1_IRQn );

   EI();

   return bRet;
 }

 static bool ADC_Start( uint32_t chid )
 {
   ADC_ChannelConfTypeDef sConfig;
 
   DI();
    /* Configure ADC regular channel */
   sConfig.Channel      = chid;
   sConfig.Rank         = ADC_REGULAR_RANK_1;
   sConfig.SamplingTime = ADC_SAMPLETIME_4CYCLES;   
      
   bool bRet = ( HAL_OK == HAL_ADC_ConfigChannel(&Adc1Handle, &sConfig) );
   EI();

   return bRet;
 }

 static bool ADC_Measure( uint32_t timeout, tADCValue_t * pResult )
 {
    bool bResult = false;
    
    if( HAL_OK == HAL_ADC_Start( &Adc1Handle ) )
    {
        // ADC performs fast conversion within 4 cycles: 2ms timeout is enough if no high-priority interrupts fires
        if( HAL_OK == HAL_ADC_PollForConversion( &Adc1Handle, timeout ) )
        {
            DI();

            if( HAL_ADC_ERROR_NONE == HAL_ADC_GetError( &Adc1Handle ) )
            {
                ADCResult = (tADCValue_t)HAL_ADC_GetValue( &Adc1Handle );

                bResult = true;
            }
            else
            {
                ADCResult = (tADCValue_t)(-2L);
            }

            if( bResult )
            {
                if( pResult )
                   *pResult = ADCResult;
            }

            EI();  
        }
    }
    
    return bResult;
 }

 static void ADC_Stop( uint32_t chid )
 {
    // To deconfigure the channel the ADC_DeInit() call is required.

    (void)chid; // no operations performed
 }

 static void ADC_DeInit()
 {
    DI();

    HAL_NVIC_DisableIRQ( ADC1_IRQn );

    HAL_ADC_Stop( &Adc1Handle );

    HAL_ADC_DeInit( &Adc1Handle );

    __HAL_RCC_ADC1_CLK_DISABLE();

    EI();
 }

static uint32_t ADC_GetVoltage(tADCValue_t * pResult)
{
    return (uint32_t)((float)(*pResult * ADC_VRED_V / 0xFFF) * 10 / KDIVIDER());
}

 void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc)
 {
 }

 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
 {
 }