#include "stm32g0xx.h"
#include "lamp_i2c_clock.h"
#include "lamp_diagnostic_runtime.h"
#include "lamp_driver_diagnostics.h"
#include "lamp_i2c_owner.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
GPIO_TypeDef a,b,c;SYSCFG_TypeDef s;RCC_TypeDef r;I2C_TypeDef i;
static bool pins=true;
static uint32_t offset;
static lg_guard frame_control;static lamp_current_profile frame_profile;
bool lamp_i2c_pins_available(void){return pins;}
static void gpio_effect(void){
 uint32_t av=a.BSRR;
 a.ODR=(a.ODR|(av&0xffffu))&~(av>>16);
 a.IDR=(a.IDR&~((1u<<4)|(1u<<7)|(1u<<9)|(1u<<11)))|
       (a.ODR&((1u<<4)|(1u<<7)|(1u<<9)|(1u<<11)));a.BSRR=0;
 uint32_t v=b.BSRR;b.ODR=(b.ODR|(v&0xffffu))&~(v>>16);b.IDR=(b.IDR&~(1u<<9))|(b.ODR&(1u<<9));b.BSRR=0;
}
static void service(uint32_t now){frame_control.last_poll_ms=now+offset;lamp_diagnostic_runtime_poll(now+offset);gpio_effect();assert(a.BSRR==0);}
static bool test_get(uint32_t now,pca_async_snapshot *out){return lamp_diagnostic_runtime_get(now+offset,out);}
static void complete_configuration(void){
 unsigned tick=0;
 for(unsigned n=0;n<174;n++){
  assert(((i.CR2&I2C_CR2_NBYTES)>>I2C_CR2_NBYTES_Pos)==2);
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(6+tick++/20);assert(i.TXDR==n%58);
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(6+tick++/20);assert(i.TXDR==0);
  i.ISR=I2C_ISR_STOPF;service(6+tick++/20);
  i.ISR=0;service(6+tick++/20);
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(6+tick++/20);assert(i.TXDR==n%58);
  i.ISR=I2C_ISR_TC|I2C_ISR_BUSY;service(6+tick++/20);
  i.RXDR=n%58==1?5:0;i.ISR=I2C_ISR_RXNE|I2C_ISR_BUSY;service(6+tick++/20);
  i.ISR=I2C_ISR_STOPF;service(6+tick++/20);
  if(n<173){
   if(n%58==0){i.ISR=0;tick+=40;service(6+tick++/20);}
   i.ISR=0;service(6+tick++/20);
  }
 }
 offset=(tick-1)/20;
 assert(lamp_diagnostic_runtime_state()==LDR_READING);
}
static void fault_expected(uint32_t now){
 pca_async_snapshot out;memset(&out,0x5a,sizeof out);pca_async_snapshot old=out;
 assert(lamp_diagnostic_runtime_state()==LDR_FAULT);assert((b.ODR&(1u<<9))==0);
 assert((a.ODR&((1u<<4)|(1u<<7)|(1u<<9)|(1u<<11)))==(1u<<7));
 assert(!lamp_diagnostic_runtime_needs_poll());assert(!test_get(now,&out));assert(memcmp(&out,&old,sizeof out)==0);
 uint32_t cr2=i.CR2;service(now+1);assert(i.CR2==cr2);assert(lamp_diagnostic_runtime_state()==LDR_FAULT);
}
static uint8_t frame_value(unsigned reg,unsigned chip){
 if(reg==1)return 5;
 if(reg>=2 && reg<=7){static const uint8_t last[6]={0xaa,0xaa,0xaa,2,0,0};return chip==2?last[reg-2]:0xaa;}
 if(reg>=10 && reg<=33){unsigned ch=chip*24+reg-10;return ch<61?(uint8_t)(ch+1):0;}
 if(reg>=34 && reg<=57)return chip*24+reg-34<61?32:0;
 return 0;
}
static void qualify_fixture(uint32_t now){
 frame_control.configured=frame_control.armed=frame_control.output_enable=true;
 frame_control.hand_present=frame_control.vision_status_seen=frame_control.frame_active=true;
 frame_control.mode=LG_AI;frame_control.session_id=42;frame_control.cfg.channels=61;
 frame_control.cfg.calibration_id=99;frame_control.cfg.max_poll_gap_ms=5;frame_control.cfg.ticket_lifetime_ms=1000;
 frame_control.run_duration_ms=2000;
 frame_control.last_poll_ms=frame_control.vision_status_ms=frame_control.frame_issued_ms=frame_control.run_started_ms=now+offset;
 frame_control.inputs=(lg_inputs){true,true,true,true,true};
 frame_profile.calibration_id=99;frame_profile.physically_qualified=true;
 for(unsigned j=0;j<61;j++){frame_control.pwm[j]=(uint8_t)(j+1);frame_profile.iref[j]=32;}
}
static void schedule_test(uint32_t now,unsigned scenario){
 qualify_fixture(now);pca_async_snapshot out;
 uint32_t cr2=i.CR2;
 frame_profile.physically_qualified=false;
 assert(!lamp_diagnostic_runtime_prepare_frame(&frame_control,&frame_profile,now+offset));
 assert(i.CR2==cr2 && test_get(now,&out));frame_profile.physically_qualified=true;
 int alien=0;assert(lamp_i2c_owner_acquire(&alien));
 assert(!lamp_diagnostic_runtime_prepare_frame(&frame_control,&frame_profile,now+offset));
 assert(lamp_i2c_owner_is(&alien) && test_get(now,&out));lamp_i2c_owner_release(&alien);
 assert(lamp_diagnostic_runtime_prepare_frame(&frame_control,&frame_profile,now+offset));
 assert(lamp_diagnostic_runtime_state()==LDR_FRAME_STAGING && lamp_diagnostic_runtime_needs_poll());
 assert(!test_get(now,&out));assert(!lamp_driver_diagnostics_pause(now+offset));
 /* Paused diagnostics cannot alter registers or publish the old snapshot. */
 lamp_driver_diagnostics_poll(now+offset+100);assert(i.CR2==cr2);
 unsigned tick=0;i.ISR=0;service(now);
 assert(!lamp_driver_diagnostics_resume(now+offset)); /* writer owns bus */
 if(scenario==601){a.ODR&=~(1u<<7);a.IDR&=~(1u<<7);service(now);fault_expected(now);return;}
 if(scenario==602){frame_control.armed=false;service(now);fault_expected(now);return;}
 if(scenario==603){i.ISR=I2C_ISR_NACKF|I2C_ISR_BUSY;service(now);fault_expected(now);return;}
 if(scenario==604){i.TIMINGR^=1;service(now);fault_expected(now);return;}
 if(scenario==605){service(now+6);fault_expected(now+6);return;}
 for(unsigned n=0;n<174;n++){
  unsigned reg=n%58,chip=n/58;uint8_t value=frame_value(reg,chip);
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(now+tick++/20);assert(i.TXDR==reg);
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(now+tick++/20);assert(i.TXDR==(reg==1?0:value));
  i.ISR=I2C_ISR_STOPF;service(now+tick++/20);
  i.ISR=0;service(now+tick++/20);
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(now+tick++/20);
  i.ISR=I2C_ISR_TC|I2C_ISR_BUSY;service(now+tick++/20);
  i.RXDR=value;if(scenario==606 && n==173)i.RXDR^=1;
  i.ISR=I2C_ISR_RXNE|I2C_ISR_BUSY;service(now+tick++/20);
  i.ISR=I2C_ISR_STOPF;service(now+tick++/20);
  if(scenario==606 && n==173){fault_expected(now+(tick-1)/20);return;}
  if(n<173){
   assert(lamp_diagnostic_runtime_state()==LDR_FRAME_STAGING);
   if(reg==0){i.ISR=0;service(now+tick++/20);tick+=40;service(now+tick++/20);}
   i.ISR=0;service(now+tick++/20);
  }
 }
 now+=(tick-1)/20;
 assert(lamp_diagnostic_runtime_state()==LDR_READING);
 assert(lamp_diagnostic_runtime_frame_ready(now+offset));assert(!test_get(now,&out));
 assert(!lamp_driver_diagnostics_resume(now+offset)); /* already resumed */
 /* A new complete bank is required; old sequence/timestamps are not reused. */
 tick=0;
 for(unsigned n=0;n<24;n++){
  i.ISR=0;service(now+tick++/16);
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(now+tick++/16);
  i.ISR=I2C_ISR_TC|I2C_ISR_BUSY;service(now+tick++/16);
  i.RXDR=n<3 || n>=21?5:0;i.ISR=I2C_ISR_RXNE|I2C_ISR_BUSY;service(now+tick++/16);
  i.ISR=I2C_ISR_STOPF;service(now+tick++/16);
 }
 assert(test_get(now+(tick-1)/16,&out));assert(out.sequence==3);
 assert(!lamp_diagnostic_runtime_frame_ready(now+(tick-1)/16+offset));
 puts("PASS integrated full-bank pause, 174 frame matches, diagnostic resume and new sequence; synthetic permissions only");
}
static void corrupt(unsigned n){
 switch(n){
 case 0:a.ODR|=1u<<4;a.IDR|=1u<<4;break;
 case 1:a.ODR&=~(1u<<7);a.IDR&=~(1u<<7);break;
 case 2:a.ODR|=1u<<11;a.IDR|=1u<<11;break;
 case 3:a.MODER&=~(3u<<8);break;
 case 4:a.MODER&=~(3u<<14);break;
 case 5:a.MODER&=~(3u<<22);break;
 case 6:a.OTYPER|=1u<<4;break;
 case 7:a.OTYPER|=1u<<7;break;
 case 8:a.OTYPER|=1u<<11;break;
 case 9:r.IOPENR&=~RCC_IOPENR_GPIOAEN;break;
 case 10:r.IOPENR&=~RCC_IOPENR_GPIOBEN;break;
 case 11:b.MODER&=~(3u<<18);break;
 case 12:b.OTYPER|=1u<<9;break;
 case 13:b.IDR^=1u<<9;break;
 case 14:b.ODR^=1u<<9;break;
 case 15:r.CR&=~RCC_CR_HSIRDY;break;
 case 16:r.CCIPR=0;break;
 case 17:i.TIMINGR^=1;break;
 case 18:i.CR1|=I2C_CR1_ANFOFF;break;
 case 19:pins=false;break;
 case 20:a.IDR^=1u<<4;break;
 case 21:a.IDR^=1u<<7;break;
 case 22:a.IDR^=1u<<11;break;
 case 23:r.APBENR2&=~RCC_APBENR2_SYSCFGEN;break;
 case 24:s.CFGR1|=SYSCFG_CFGR1_PA11_RMP;break;
 case 25:s.CFGR1|=SYSCFG_CFGR1_PA12_RMP;break;
 case 26:r.BDCR|=RCC_BDCR_LSEON;break;
 case 27:c.MODER=1u<<28;break;
 case 28:r.IOPENR&=~RCC_IOPENR_GPIOCEN;break;
 case 29:c.PUPDR=1u<<28;break;
 default:assert(0);
 }
}
int main(int argc,char **argv){
 unsigned scenario=argc>1?(unsigned)atoi(argv[1]):0;
 a.MODER=(1u<<8)|(1u<<14)|(1u<<22);a.ODR=a.IDR=1u<<7;
 b.MODER=1u<<18;b.IDR=(1u<<6)|(1u<<7);
 r.APBENR2=RCC_APBENR2_SYSCFGEN;c.MODER=3u<<28;r.IOPENR=RCC_IOPENR_GPIOAEN|RCC_IOPENR_GPIOBEN|RCC_IOPENR_GPIOCEN;r.CR=RCC_CR_HSION|RCC_CR_HSIRDY;r.APBENR1=RCC_APBENR1_I2C1EN;
 assert(lamp_i2c_clock_init());
 if(scenario>=100 && scenario<130)corrupt(scenario-100);
 if(scenario==404){
  lamp_diagnostic_runtime_init(UINT32_MAX-2u);gpio_effect();
  service(UINT32_MAX-1u);service(UINT32_MAX);service(0);
  assert(lamp_diagnostic_runtime_state()==LDR_RESET_RECOVERY);
  service(1);service(2);service(3);assert(lamp_diagnostic_runtime_state()==LDR_CONFIGURING);
  assert(i.CR2&I2C_CR2_START);return 0;
 }
 lamp_diagnostic_runtime_init(0);gpio_effect();
 if(scenario>=100 && scenario<130){fault_expected(0);return 0;}
 assert(lamp_diagnostic_runtime_state()==LDR_RESET_HOLD);
 service(0);service(1);service(2);assert(i.CR2==0 && !(b.ODR&(1u<<9)));
 service(3);assert(lamp_diagnostic_runtime_state()==LDR_RESET_RECOVERY && (b.ODR&(1u<<9)));
 service(4);service(5);assert(i.CR2==0);
 if(scenario>=200 && scenario<230){corrupt(scenario-200);service(6);fault_expected(6);return 0;}
 service(6);assert(lamp_diagnostic_runtime_state()==LDR_CONFIGURING);complete_configuration();assert(lamp_diagnostic_runtime_state()==LDR_READING && (i.CR2&I2C_CR2_START));
 assert(lamp_diagnostic_runtime_needs_poll());
 if(scenario>=600 && scenario<=606){
  qualify_fixture(6);uint32_t before=i.CR2;
  assert(!lamp_diagnostic_runtime_prepare_frame(&frame_control,&frame_profile,6+offset));
  assert(!lamp_driver_diagnostics_pause(6+offset));assert(i.CR2==before);
 }
 if(scenario>=300 && scenario<330){corrupt(scenario-300);service(6);fault_expected(6);return 0;}
 if(scenario==400){service(12);fault_expected(12);return 0;}
 if(scenario==401){service(5);fault_expected(5);return 0;}
 if(scenario==402){lamp_diagnostic_runtime_init(6);gpio_effect();fault_expected(6);return 0;}
 if(scenario==403){i.ISR=I2C_ISR_BUSY;service(6);service(11);service(12);fault_expected(12);return 0;}
 unsigned tick=0;uint32_t now=6; pca_async_snapshot out;
 for(unsigned tx=0;tx<48;tx++){
  if(tx){i.ISR=0;now=6+tick/16u;service(now);tick++;}
  if(scenario>=500 && scenario<524 && tx==scenario-500){i.ISR=I2C_ISR_NACKF|I2C_ISR_BUSY;service(now);fault_expected(now);assert(!(i.CR1&I2C_CR1_PE));return 0;}
  i.ISR=I2C_ISR_TXIS|I2C_ISR_BUSY;service(6+tick++/16u);
  i.ISR=I2C_ISR_TC|I2C_ISR_BUSY;service(6+tick++/16u);
  i.RXDR=tx%24<3 || tx%24>=21?5:0;i.ISR=I2C_ISR_RXNE|I2C_ISR_BUSY;service(6+tick++/16u);
  assert(!test_get(6+tick/16u,&out));
  i.ISR=I2C_ISR_STOPF;now=6+tick++/16u;service(now);
  assert(lamp_diagnostic_runtime_state()==LDR_READING);
  bool valid=test_get(now,&out);assert(valid==((tx+1)%24==0));
  if(valid){assert(out.sequence==(tx+1)/24);for(unsigned j=0;j<3;j++)assert(out.chip[j].eligible_mask==0);}
 }
 if(scenario>=600 && scenario<=606){schedule_test(now,scenario);return 0;}
 assert(test_get(now,&out));
 assert(!test_get(now+21,&out));
 puts("PASS runtime reset/recovery, two diagnostic banks, zero eligible masks; MMIO simulation only");
}
