#include "lamp_adc.h"
#include "stm32g0xx.h"
#include <stddef.h>
/* A39 U1 physical pads 8/12/13 = PA1/PA5/PA6; VREFINT = channel13.
 * Cold HSI16, no bus prescaling, synchronous ADC clock PCLK/2 = 8MHz.
 * ST LL gives regulator20us, VREF12us, post-calibration2ADC cycles.
 * Separate >=2 tick waits cover tick-phase uncertainty. No busy-wait. */
enum phase { OFF, REG_WAIT, CAL_WAIT, CAL_SETTLE, READY_WAIT,
             SELECT_CHANNEL, CHANNEL_WAIT, CONVERSION_WAIT, FRAME_IDLE, FAILED };
static enum phase state;
static const uint8_t channels[4]={1u,5u,6u,13u};
static uint16_t pending[4];
static uint32_t entered, last_poll, batch_start;
static unsigned slot;
static bool valid;
static lamp_adc_frame frame;
#define ANALOG_MASK ((3u<<2u)|(3u<<10u)|(3u<<12u))
#define PIN_MASK ((1u<<1u)|(1u<<5u)|(1u<<6u))
#define TIMEOUT_MS 5u
#define MAX_GAP_MS 5u
#define MAX_FRAME_AGE_MS 20u
#define FLAGS (ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_OVR|ADC_ISR_CCRDY)
static void clear_flags(uint32_t flags) { ADC1->ISR=flags; }
static bool clock_ok(void) {
    return (RCC->CR & (RCC_CR_HSIRDY|RCC_CR_HSIDIV))==RCC_CR_HSIRDY &&
           (RCC->CFGR & (RCC_CFGR_SWS|RCC_CFGR_HPRE|RCC_CFGR_PPRE))==0u;
}
static bool config_ok(void) {
    return clock_ok() && (RCC->APBENR2 & RCC_APBENR2_ADCEN)!=0u &&
        (RCC->APBRSTR2 & RCC_APBRSTR2_ADCRST)==0u &&
        (RCC->IOPENR & RCC_IOPENR_GPIOAEN)!=0u &&
        (GPIOA->MODER & ANALOG_MASK)==ANALOG_MASK &&
        (GPIOA->PUPDR & ANALOG_MASK)==0u &&
        !((GPIOA->LCKR & (1u<<16u))!=0u && (GPIOA->LCKR & PIN_MASK)!=0u) &&
        ADC1->CFGR1==0u && ADC1->CFGR2==ADC_CFGR2_CKMODE_0 &&
        ADC1->SMPR==ADC_SMPR_SMP1 && ADC1_COMMON->CCR==ADC_CCR_VREFEN &&
        (ADC1->CR & ADC_CR_ADVREGEN)!=0u;
}
static void fail(void) {
    valid=false; state=FAILED;
    if ((RCC->APBENR2 & RCC_APBENR2_ADCEN)!=0u &&
        (ADC1->CR & ADC_CR_ADSTART)!=0u) ADC1->CR|=ADC_CR_ADSTP;
}
static void enter(enum phase next,uint32_t now) { state=next; entered=now; }
void lamp_adc_init(uint32_t now) {
    valid=false; frame=(lamp_adc_frame){0}; slot=0u; last_poll=now; state=OFF;
    if (!clock_ok() || ((GPIOA->LCKR & (1u<<16u))!=0u &&
                            (GPIOA->LCKR & PIN_MASK)!=0u)) {fail();return;}
    RCC->IOPENR|=RCC_IOPENR_GPIOAEN; (void)RCC->IOPENR;
    GPIOA->MODER|=ANALOG_MASK;
    GPIOA->PUPDR&=~ANALOG_MASK;
    RCC->APBENR2|=RCC_APBENR2_ADCEN;
    RCC->APBRSTR2|=RCC_APBRSTR2_ADCRST; (void)RCC->APBRSTR2;
    RCC->APBRSTR2&=~RCC_APBRSTR2_ADCRST; (void)RCC->APBRSTR2;
    /* ADC reset is limited to this peripheral, with no other ADC owner. */
    ADC1->IER=0u; ADC1->CFGR1=0u; ADC1->CFGR2=ADC_CFGR2_CKMODE_0;
    ADC1->SMPR=ADC_SMPR_SMP1; ADC1_COMMON->CCR=ADC_CCR_VREFEN;
    clear_flags(FLAGS|ADC_ISR_ADRDY|ADC_ISR_EOCAL);
    ADC1->CR=ADC_CR_ADVREGEN;
    enter(REG_WAIT,now);
}
void lamp_adc_poll(uint32_t now) {
    if(state==OFF || state==FAILED) return;
    if(now-last_poll>MAX_GAP_MS || !config_ok()) {fail();return;}
    last_poll=now;
    if ((ADC1->ISR & ADC_ISR_OVR)!=0u) {fail();return;}
    uint32_t age=now-entered;
    /* A late completion cannot bypass its deadline. */
    if ((state==CAL_WAIT || state==READY_WAIT || state==CHANNEL_WAIT ||
         state==CONVERSION_WAIT) && age>TIMEOUT_MS) {fail();return;}
    switch(state) {
    case REG_WAIT:
        if(age>=2u) {ADC1->CR|=ADC_CR_ADCAL;enter(CAL_WAIT,now);} break;
    case CAL_WAIT:
        if((ADC1->CR & ADC_CR_ADCAL)==0u) enter(CAL_SETTLE,now);
        break;
    case CAL_SETTLE:
        if(age>=2u) {clear_flags(ADC_ISR_ADRDY);ADC1->CR|=ADC_CR_ADEN;
                    enter(READY_WAIT,now);} break;
    case READY_WAIT:
        if((ADC1->ISR & ADC_ISR_ADRDY)!=0u && (ADC1->CR & ADC_CR_ADEN)!=0u)
            enter(SELECT_CHANNEL,now);
        break;
    case SELECT_CHANNEL:
        if((ADC1->CR & (ADC_CR_ADSTART|ADC_CR_ADSTP|ADC_CR_ADCAL))!=0u ||
           (ADC1->CR & ADC_CR_ADEN)==0u) {fail();break;}
        if(slot==0u) batch_start=now;
        clear_flags(FLAGS); ADC1->CHSELR=1u<<channels[slot];
        enter(CHANNEL_WAIT,now); break;
    case CHANNEL_WAIT:
        if(ADC1->CHSELR!=(1u<<channels[slot]) || (ADC1->CR & ADC_CR_ADEN)==0u)
            {fail();break;}
        if((ADC1->ISR & ADC_ISR_CCRDY)!=0u) {
            ADC1->CR|=ADC_CR_ADSTART;enter(CONVERSION_WAIT,now);
        } break;
    case CONVERSION_WAIT:
        if(ADC1->CHSELR!=(1u<<channels[slot]) || (ADC1->CR & ADC_CR_ADEN)==0u)
            {fail();break;}
        if((ADC1->ISR & (ADC_ISR_EOC|ADC_ISR_EOS))==(ADC_ISR_EOC|ADC_ISR_EOS) &&
           (ADC1->CR & ADC_CR_ADSTART)==0u) {
            pending[slot]=(uint16_t)(ADC1->DR & 0xfffu);
            clear_flags(ADC_ISR_EOC|ADC_ISR_EOS);
            if(++slot==4u) {
                if(now-batch_start>MAX_FRAME_AGE_MS) {fail();break;}
                frame=(lamp_adc_frame){pending[0],pending[1],pending[2],pending[3],
                                      batch_start,now,frame.sequence+1u};
                valid=true;slot=0u;enter(FRAME_IDLE,now);
            } else enter(SELECT_CHANNEL,now);
        } break;
    case FRAME_IDLE: if(age>=2u) enter(SELECT_CHANNEL,now); break;
    default: fail(); break;
    }
}
bool lamp_adc_get_frame(uint32_t now,lamp_adc_frame *out) {
    if(out==NULL || !valid || state==FAILED || !config_ok() ||
       (ADC1->ISR & ADC_ISR_OVR)!=0u || (ADC1->CR & ADC_CR_ADEN)==0u ||
       now-frame.first_sample_ms>MAX_FRAME_AGE_MS || now-last_poll>MAX_GAP_MS)
        return false;
    *out=frame; return true;
}
bool lamp_adc_failed(void) { return state==FAILED; }
