#include "stm32g0xx.h"
#include "lamp_adc.h"
#include "lamp_measurement.h"
#include "lamp_ntc.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
GPIO_TypeDef a;
RCC_TypeDef r;
ADC_TypeDef adc;
ADC_Common_TypeDef common;
static uint32_t now,cal_due,ready_due,channel_due,conv_due;
static unsigned cal_delay,ready_delay,channel_delay,conv_delay;
static bool cal_pending,ready_pending,channel_pending,conv_pending;
static bool freeze_cal,freeze_ready,freeze_channel,freeze_conv,inject_overrun;
static unsigned starts;
static const unsigned sequence_channels[4]={1,5,6,13};
static bool due(uint32_t at) {return (int32_t)(now-at)>=0;}
void test_clear_flags(uint32_t flags) {adc.ISR&=~flags;}
void test_set_channel(uint32_t mask) {
 assert(!(adc.CR&(ADC_CR_ADSTART|ADC_CR_ADCAL)));
 adc.CHSELR=mask;channel_pending=true;channel_due=now+channel_delay;
 assert(mask==(1u<<sequence_channels[starts%4]));
}
static void hardware_before(void) {
 if(cal_pending && !freeze_cal && due(cal_due)){adc.CR&=~ADC_CR_ADCAL;cal_pending=false;}
 if(ready_pending && !freeze_ready && due(ready_due)){adc.ISR|=ADC_ISR_ADRDY;ready_pending=false;}
 if(channel_pending && !freeze_channel && due(channel_due)){adc.ISR|=ADC_ISR_CCRDY;channel_pending=false;}
 if(conv_pending && !freeze_conv && due(conv_due)){
  adc.ISR|=ADC_ISR_EOC|ADC_ISR_EOS;
  if(inject_overrun)adc.ISR|=ADC_ISR_OVR;
  adc.DR=((starts-1u)%4u==3u)?1489u:((starts-1u)%4u==0u)?2048u:100u+sequence_channels[(starts-1u)%4u];
  adc.CR&=~ADC_CR_ADSTART;conv_pending=false;
 }
}
static void tick(void) {
 hardware_before();uint32_t old=adc.CR;
 lamp_adc_poll(now);
 if(!(old&ADC_CR_ADCAL) && (adc.CR&ADC_CR_ADCAL)){
  cal_pending=true;cal_due=now+cal_delay;
 }
 if(!(old&ADC_CR_ADEN) && (adc.CR&ADC_CR_ADEN)){
  ready_pending=true;ready_due=now+ready_delay;
 }
 if(!(old&ADC_CR_ADSTART) && (adc.CR&ADC_CR_ADSTART)){
  /* Hardware ignores a premature ADSTART: this assertion detects it. */
  assert(!channel_pending && (adc.ISR&ADC_ISR_CCRDY));
  ++starts;conv_pending=true;conv_due=now+conv_delay;
 }
 ++now;
}
static void reset(uint32_t start) {
 memset(&a,0,sizeof a);memset(&r,0,sizeof r);memset(&adc,0,sizeof adc);memset(&common,0,sizeof common);
 cal_pending=ready_pending=channel_pending=conv_pending=false;
 freeze_cal=freeze_ready=freeze_channel=freeze_conv=inject_overrun=false;
 starts=0;now=start;r.CR=RCC_CR_HSIRDY;
 a.MODER=0x51234567u;a.PUPDR=0x87654321u;a.BSRR=0x12345678u;
 uint32_t m=a.MODER,p=a.PUPDR,b=a.BSRR;
 lamp_adc_init(now);
 const uint32_t mask=(3u<<2)|(3u<<10)|(3u<<12);
 assert((a.MODER&mask)==mask && (a.PUPDR&mask)==0u);
 assert((a.MODER&~mask)==(m&~mask) && (a.PUPDR&~mask)==(p&~mask) && a.BSRR==b);
 assert(adc.CFGR2==ADC_CFGR2_CKMODE_0 && adc.SMPR==ADC_SMPR_SMP1);
 assert(adc.IER==0u && common.CCR==ADC_CCR_VREFEN && !lamp_adc_failed());
}
static lamp_adc_frame get_good(void) {
 lamp_adc_frame out={0};bool ok=false;
 for(unsigned i=0;i<100 && !ok;i++){
  tick();ok=lamp_adc_get_frame(now-1u,&out);
  if(starts<4u)assert(!ok); /* No partial-frame publication. */
 }
 assert(ok && !lamp_adc_failed());
 assert(out.ntc_raw==2048u && out.led_v_raw==105u && out.led_i_raw==106u && out.vrefint_raw==1489u);
 lamp_measurement measured;
 assert(lamp_measurement_from_frame(&out,1638u,now-1u,&measured)==LAMP_MEAS_OK);
 assert(measured.estimated_vdda_uv>3200000u && measured.estimated_vdda_uv<3400000u);
 assert(measured.nominal_led_uv>160000u && measured.nominal_led_uv<180000u);
 assert((measured.quality_flags&LAMP_MEAS_CURRENT_OUTSIDE_GAIN_WINDOW)!=0u);
 int32_t temperature;
 assert(lamp_ntc_candidate_temperature(out.ntc_raw,&temperature) && temperature>=2490 && temperature<=2510);
 assert(out.sequence==1u && out.completed_ms-out.first_sample_ms<=20u);
 return out;
}
int main(void) {
 unsigned cases=0;
 for(unsigned c=0;c<2;c++)for(unsigned rd=0;rd<2;rd++)
 for(unsigned ch=0;ch<3;ch++)for(unsigned cv=0;cv<3;cv++)
 for(unsigned wrap=0;wrap<2;wrap++){
  cal_delay=c;ready_delay=rd;channel_delay=ch;conv_delay=cv;
  reset(wrap?UINT32_MAX-10u:0u);lamp_adc_frame f=get_good();
  assert(!lamp_adc_get_frame(now,NULL));
  lamp_adc_frame untouched={.sequence=999u};
  assert(!lamp_adc_get_frame(f.first_sample_ms+21u,&untouched) && untouched.sequence==999u);
  /* Poll gap must invalidate even if all raw values look plausible. */
  now+=6u;tick();assert(lamp_adc_failed());assert(!lamp_adc_get_frame(now,&untouched));
  ++cases;
 }
 cal_delay=ready_delay=channel_delay=conv_delay=1u;
 for(unsigned f=0;f<5;f++){
  reset(0);freeze_cal=f==0;freeze_ready=f==1;freeze_channel=f==2;freeze_conv=f==3;inject_overrun=f==4;
  for(unsigned i=0;i<50;i++)tick();
  assert(lamp_adc_failed());lamp_adc_frame out;assert(!lamp_adc_get_frame(now,&out));
 }
 for(unsigned fault=0;fault<9;fault++){
  reset(0);get_good();
  switch(fault){
   case 0:r.CR|=RCC_CR_HSIDIV_0;break;
   case 1:r.CFGR|=RCC_CFGR_PPRE;break;
   case 2:r.APBENR2&=~RCC_APBENR2_ADCEN;break;
   case 3:a.MODER&=~(3u<<10u);break;
   case 4:a.PUPDR|=1u<<12u;break;
   case 5:adc.CFGR1=ADC_CFGR1_CONT;break;
   case 6:common.CCR=0;break;
   case 7:a.LCKR=(1u<<16u)|(1u<<1u);break;
   case 8:adc.ISR|=ADC_ISR_OVR;break;
  }
  tick();assert(lamp_adc_failed());
 }
 reset(0);a.LCKR=(1u<<16u)|(1u<<5u);lamp_adc_init(now);assert(lamp_adc_failed());
 reset(0);r.CFGR=RCC_CFGR_SWS_HSE;lamp_adc_init(now);assert(lamp_adc_failed());
 /* Backward time is rejected as an excessive unsigned gap. */
 reset(100);now=99;tick();assert(lamp_adc_failed());
 /* Late channel-ready flag cannot override the stage deadline. */
 reset(0);freeze_channel=true;while(!channel_pending)tick();now+=6u;adc.ISR|=ADC_ISR_CCRDY;tick();assert(lamp_adc_failed() && starts==0);
 assert(cases==72u);
 puts("PASS: 72 delay/wrap frame cases; physical channel order; GPIO isolation; partial/stale frames; stage timeouts, overrun, clock/config faults, locked pins and backward/late time rejected. MMIO simulation only.");
 return 0;
}
