#include "soft_i2c.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
typedef struct {
 bool scl_release,sda_release,clock_high,active,ack,stuck_scl,stuck_sda,freeze,stretch_active,data_stuck;
 uint16_t time,raised;unsigned stretch,step,starts,stops,bits,bytes,nack,polls;
 uint8_t accum,packet[32];
} wire;
static uint16_t ticks(void*c){wire*w=c;w->polls++;if(!w->freeze)w->time+=(uint16_t)w->step;return w->time;}
static void scl(void*c,bool release){wire*w=c;w->scl_release=release;if(!release){w->clock_high=false;w->ack=false;}else w->raised=w->time;}
static bool read_scl(void*c){wire*w=c;if(!w->scl_release||w->stuck_scl||((!w->stretch_active||w->active)&&((uint16_t)(w->time-w->raised)<w->stretch)))return false;
 if(!w->clock_high){w->clock_high=true;if(w->active){if(w->bits==8){w->ack=true;w->bits=0;assert(w->bytes<32);w->packet[w->bytes++]=w->accum;w->accum=0;}else{w->ack=false;w->accum=(uint8_t)((w->accum<<1)|w->sda_release);++w->bits;}}}return true;}
static bool read_sda(void*c){wire*w=c;if(w->stuck_sda||(w->data_stuck&&w->active&&!w->ack))return false;if(w->ack)return w->bytes==w->nack;return w->sda_release;}
static void sda(void*c,bool release){wire*w=c;
 if(w->clock_high&&w->sda_release!=release){if(!release){w->starts++;w->active=true;w->bits=0;w->bytes=0;w->accum=0;}else{w->stops++;w->active=false;w->ack=false;}}
 w->sda_release=release;
}
static si_bus make(wire*w){return(si_bus){w,scl,sda,read_scl,read_sda,ticks,SI_OK,0,0};}
int main(void){
 uint8_t payload[17];for(unsigned i=0;i<17;i++)payload[i]=(uint8_t)(i*15);
 for(unsigned addr=0;addr<128;addr++)for(unsigned length=1;length<=17;length++){
  wire w={.step=1,.scl_release=true,.sda_release=true};si_bus b=make(&w);
  assert(si_write(&b,(uint8_t)addr,payload,length));assert(b.status==SI_OK);
  assert(w.starts==1&&w.stops==1&&w.bytes==length+1&&w.packet[0]==addr*2);
  assert(!memcmp(&w.packet[1],payload,length)&&w.scl_release&&w.sda_release);
  assert(w.time<3000);
 }
 for(unsigned nack=1;nack<=18;nack++){
  wire w={.step=1,.scl_release=true,.sda_release=true,.nack=nack};si_bus b=make(&w);
  assert(!si_write(&b,0x3c,payload,17)&&b.status==SI_NACK);
  assert(w.bytes==nack&&w.stops==1&&w.scl_release&&w.sda_release);
 }
 for(unsigned failure=0;failure<5;failure++){
  wire w={.step=1,.scl_release=true,.sda_release=true};
  if(failure==0)w.stuck_scl=true;
  if(failure==1)w.stuck_sda=true;
  if(failure==2){w.stretch=101;w.stretch_active=true;}
  if(failure==3)w.freeze=true;
  if(failure==4)w.step=20;
  si_bus b=make(&w);assert(!si_write(&b,0x3c,payload,17));
  assert(w.scl_release&&w.sda_release&&b.polls<=20002);
  if(failure==2)assert(b.status==SI_STRETCH_TIMEOUT);
  if(failure==3)assert(b.status==SI_TIMER_STALLED);
  if(failure==4)assert(b.status==SI_DEADLINE);
 }
 wire held={.step=1,.scl_release=true,.sda_release=true,.data_stuck=true};si_bus hb=make(&held);
 assert(!si_write(&hb,0x3c,payload,1)&&hb.status==SI_DATA_HELD);
 wire stretched={.step=1,.scl_release=true,.sda_release=true,.stretch=10,.stretch_active=true};si_bus sb=make(&stretched);
 assert(si_write(&sb,0x3c,payload,1)&&sb.status==SI_OK);
 /* Stretch only after START: initial pullup is already high. */
 wire w={.step=1,.scl_release=true,.sda_release=true,.clock_high=true,.time=65500};si_bus b=make(&w);
 assert(si_write(&b,0x3c,payload,17)&&b.status==SI_OK);assert(w.time<65500);
 for(unsigned value=0;value<256;value++){
  wire z={.step=1,.scl_release=true,.sda_release=true};si_bus q=make(&z);uint8_t v=(uint8_t)value;
  assert(si_write(&q,0x3c,&v,1)&&z.packet[1]==v);
 }
 w=(wire){0};b=make(&w);assert(!si_write(&b,128,payload,1)&&b.status==SI_ARGUMENT);
 assert(!si_write(&b,0x3c,payload,18));assert(!si_write(&b,0x3c,NULL,1));assert(!si_write(&b,0x3c,payload,0));
 puts("PASS wire I2C:2176 address/length waveforms, all256 byte patterns,18 ACK failures, stuck lines/timer/deadline/wrap");
}
