#include "host_stream_fixture.h"
static const cq_profile profile={3300000,3300000,2000};
static void start(uint32_t origin){streaming(origin);timer2.CNT=origin+60;assert(!drill_current_bench_begin(&profile,origin+60,false)&&drill_debug_current.state==CQ_IDLE);assert(drill_current_bench_begin(&profile,origin+60,true));assert(!drill_current_bench_begin(&profile,origin+60,true)&&drill_debug_current.state==CQ_ZEROING);}
static du_sample sample(uint16_t a,uint16_t b,uint32_t now,uint32_t seq){return (du_sample){.ia_raw=a,.ib_raw=b,.armed_us=60,.observed_us=now,.sequence=seq};}
static void feed(uint16_t a,uint16_t b,uint32_t now,uint32_t seq){timer2.CNT=now;du_sample s=sample(a,b,now,seq);drill_current_feed(&s,now);}
static void calibrated(void){start(0);for(unsigned i=1;i<=256;++i)feed(2048,2048,60+i*50,i);assert(drill_debug_current.state==CQ_READY&&drill_debug_current.count==256&&drill_debug_current.zero_a_q8==2048u*256&&drill_debug_current.flags==1&&!(drill_debug_current.epoch&1));}
static void failed(cq_fault f){assert(drill_debug_current.state==CQ_FAILED&&drill_debug_current.fault==(uint32_t)f&&!(drill_debug_current.flags&2)&&!(drill_debug_current.epoch&1));stream_off();assert(!drill_current_bench_begin(&profile,timer2.CNT,true));}
int main(void){unsigned cases=0;
 for(unsigned wrap=0;wrap<2;++wrap){uint32_t base=wrap?UINT32_MAX-1000:0;start(base);for(unsigned i=1;i<=256;++i){feed((uint16_t)(2047+(i&1)),(uint16_t)(2048+(i&1)),base+60+i*50,i);assert(drill_debug_current.state==(i==256?CQ_READY:CQ_ZEROING));++cases;}assert(drill_debug_current.zero_a_q8==2047u*256+128&&drill_debug_current.zero_b_q8==2048u*256+128);feed(2048,2048,base+12910,257);assert(drill_debug_current.state==CQ_READY&&drill_debug_current.sense_a_ma==1&&drill_debug_current.sense_b_ma==-1&&drill_debug_current.reconstructed_c_ma==0&&drill_debug_current.flags==7);++cases;}
 /* Exhaustive nominal +/- current conversion compared to independent double
  * reference (rounded mA), with a 1mA fixed-point tolerance. No bench accuracy. */
 calibrated();cq_record saved=drill_debug_current;uint32_t scale=cq_scale;
 for(unsigned raw=311;raw<=3784;++raw){drill_debug_current=saved;cq_scale=scale;cq_last_poll=cq_last_sample=12860;cq_sequence=256;cq_have_sample=true;
  feed((uint16_t)raw,2048,12910,257);double expected=((double)raw-2048.0)*3300000.0*1000.0/(4095.0*300000.0);int32_t rounded=(int32_t)(expected>=0?expected+0.5:expected-0.5);int32_t got=drill_debug_current.sense_a_ma;assert(got-rounded>=-1&&got-rounded<=1);
  assert(drill_debug_current.state==((got>2000||got<-2000)?CQ_FAILED:CQ_READY));assert(drill_debug_current.reconstructed_c_ma==-got);++cases;
  /* Hard-off from one iteration is not a restart: this next iteration is an
   * independently modeled cold/peripheral fixture with the same calibrated
   * math inputs. There is no reset API in production. */
  if(drill_debug_current.state==CQ_FAILED){fixture();ready(0);timer2.CNT=60;assert(drill_dual_stream_start(60));}
 }
 for(unsigned kind=0;kind<12;++kind){start(0);if(kind==0){feed(0,2048,110,1);failed(CQ_RANGE);}if(kind==1){feed(4095,2048,110,1);failed(CQ_RANGE);}if(kind==2){feed(4096,2048,110,1);failed(CQ_RANGE);}if(kind==3){feed(1990,2048,110,1);failed(CQ_OFFSET);}if(kind==4){feed(2100,2048,110,1);failed(CQ_OFFSET);}if(kind==5){feed(2048,2048,110,1);feed(2065,2048,160,2);failed(CQ_NOISE);}if(kind==6){feed(2048,2048,110,1);feed(2048,2065,160,2);failed(CQ_NOISE);}if(kind==7){feed(2048,2048,110,1);feed(2048,2048,160,1);failed(CQ_SEQUENCE);}if(kind==8){feed(2048,2048,110,1);feed(2048,2048,160,3);failed(CQ_SEQUENCE);}if(kind==9){feed(2048,2048,110,1);feed(2048,2048,110,2);failed(CQ_SEQUENCE);}if(kind==10){timer2.CNT=161;drill_current_poll(161);failed(CQ_TIME);}if(kind==11){drill_config.control2_readback=0x42;timer2.CNT=80;drill_current_poll(80);failed(CQ_INTERLOCK);}++cases;}
 for(unsigned kind=0;kind<7;++kind){start(0);timer2.CNT=80;du_sample s=sample(2048,2048,80,1);if(kind==0)s.observed_us=49;if(kind==1)s.observed_us=81;if(kind==2)drill_current_feed(NULL,80);else if(kind==3){timer2.PSC^=1;drill_current_poll(80);}else if(kind==4){timer2.CNT=91;drill_current_poll(60);}else if(kind==5){timer2.CNT=59;drill_current_poll(59);}else if(kind==6)drill_current_abort();else drill_current_feed(&s,80);failed(kind==2?CQ_ARGUMENT:kind==6?CQ_INTERLOCK:CQ_TIME);++cases;}
 for(unsigned kind=0;kind<9;++kind){streaming(0);timer2.CNT=60;cq_profile p=profile;if(kind==0)p.adc_reference_uv=2799999;if(kind==1)p.adc_reference_uv=3600001;if(kind==2)p.drv_reference_uv=3134999;if(kind==3)p.drv_reference_uv=3465001;if(kind==4)p.limit_ma=0;if(kind==5)p.limit_ma=2001;if(kind==6){p.adc_reference_uv=2800000;assert(drill_current_bench_begin(&p,60,true)&&drill_debug_current.state==CQ_ZEROING);++cases;continue;}if(kind==7)drill_config.gain_readback=0;assert(!drill_current_bench_begin(kind==8?NULL:&p,60,true));failed(kind==6?CQ_RANGE:kind==7?CQ_INTERLOCK:CQ_ARGUMENT);++cases;}
 calibrated();feed(2500,2500,12910,257);assert(drill_debug_current.sense_a_ma<2000&&drill_debug_current.sense_b_ma<2000);failed(CQ_LIMIT);++cases;
 calibrated();feed(1596,1596,12910,257);failed(CQ_LIMIT);++cases;
 calibrated();cq_sequence=UINT32_MAX;feed(2048,2048,12910,0);assert(drill_debug_current.state==CQ_READY);++cases;
 /* 16-count noise boundary, reference extremes, and max calibration duration. */
 start(0);for(unsigned i=1;i<=256;++i)feed((uint16_t)(2048+(i&1)*16),2048,60+i*50,i);assert(drill_debug_current.state==CQ_READY&&drill_debug_current.peak_a_counts==16);++cases;
 start(0);for(unsigned i=1;i<=201;++i)feed(2048,2048,60+i*100,i);failed(CQ_TIME);++cases;
 /* Actual foreground DMA->current integration, with a service every25us and
  * one completed pair per50us. No fabricated register state is hardware proof. */
 start(0);drill_debug_stream_received=0;for(unsigned i=1;i<=512;++i){uint32_t now=60+i*25;timer2.CNT=now;if(!(i&1))transfer(0x08000800);assert(!drill_foreground_service(now));assert(drill_dual_state==DU_STREAM);++cases;}
 assert(drill_debug_current.state==CQ_READY&&drill_debug_stream_received==256);timer2.CNT=12885;(void)drill_foreground_service(12885);timer2.CNT=12910;transfer(0x08000bb8);assert(drill_foreground_service(12910));failed(CQ_LIMIT);assert(drill_debug_stream_received==256);++cases;
 /* Caller timestamp may precede actual consumer observation; fresh TIM2 wins. */
 start(0);timer2.CNT=90;du_sample fresh=sample(2048,2048,90,1);drill_current_feed(&fresh,80);assert(drill_debug_current.count==1&&drill_debug_current.state==CQ_ZEROING);++cases;
 for(unsigned k=0;k<10;++k){start(0);if(k==0)timer.CCER=1;if(k==1)timer.BDTR|=TIM_BDTR_MOE;if(k==2)ga.ODR|=1u<<5;if(k==3)ga.IDR|=1u<<8;if(k==4)gb.ODR|=1;if(k==5)gb.IDR|=2;if(k==6)ga.MODER^=1u<<16;if(k==7)ga.ODR&=~(1u<<12);if(k==8)gf.ODR&=~2u;if(k==9)ga.OTYPER|=1u<<12;timer2.CNT=80;drill_current_poll(80);failed(CQ_INTERLOCK);++cases;}
 const cq_profile refs[]={{2800000,3135000,2000},{3600000,3465000,2000},{3300000,3135000,2000},{3280000,3365000,2000}};
 for(unsigned k=0;k<4;++k){streaming(0);timer2.CNT=60;assert(drill_current_bench_begin(&refs[k],60,true));uint16_t zero=(uint16_t)(((uint64_t)refs[k].drv_reference_uv*4095u+refs[k].adc_reference_uv)/(2u*refs[k].adc_reference_uv));assert(zero!=2048);for(unsigned i=1;i<=256;++i){feed(zero,zero,60+i*50,i);assert(drill_debug_current.state==(i==256?CQ_READY:CQ_ZEROING));++cases;}assert(drill_debug_current.zero_a_q8==(uint32_t)zero*256);feed((uint16_t)(zero+123),zero,12910,257);double expected=123.0*(double)refs[k].adc_reference_uv*1000.0/(4095.0*300000.0);int32_t rounded=(int32_t)(expected+0.5);assert(drill_debug_current.state==CQ_READY&&drill_debug_current.sense_b_ma==0&&drill_debug_current.sense_a_ma-rounded>=-1&&drill_debug_current.sense_a_ma-rounded<=1);++cases;}
 printf("%u CURRENT ZERO / FIXED-POINT / RANGE / TIME / LIMIT / DMA HARD-OFF CASES PASS\n",cases);
}
