#include "soft_i2c.h"
static bool check(si_bus*b){
 if(b->polls++>=20000u){b->status=SI_TIMER_STALLED;return false;}
 if((uint16_t)(b->ticks(b->ctx)-b->started)>=3000u){b->status=SI_DEADLINE;return false;}
 return true;
}
static bool delay(si_bus*b){
 uint16_t start=b->ticks(b->ctx);
 do {if(!check(b))return false;} while((uint16_t)(b->ticks(b->ctx)-start)<5u);
 return true;
}
static bool high(si_bus*b){
 b->scl(b->ctx,true);uint16_t start=b->ticks(b->ctx);
 while(!b->read_scl(b->ctx)){
  if(!check(b))return false;
  if((uint16_t)(b->ticks(b->ctx)-start)>=100u){b->status=SI_STRETCH_TIMEOUT;return false;}
 }
 return delay(b);
}
static bool byte(si_bus*b,uint8_t v){
 for(unsigned bit=0;bit<8;bit++){
  bool one=(v&0x80u)!=0;v<<=1;
  b->sda(b->ctx,one);
  if(!delay(b)||!high(b))return false;
  if(one&&!b->read_sda(b->ctx)){b->status=SI_DATA_HELD;return false;}
  b->scl(b->ctx,false);
 }
 b->sda(b->ctx,true);
 if(!delay(b)||!high(b))return false;
 bool ack=!b->read_sda(b->ctx);b->scl(b->ctx,false);
 if(!ack){b->status=SI_NACK;return false;}return true;
}
static bool stop(si_bus*b){
 b->sda(b->ctx,false);
 if(!delay(b)||!high(b))return false;
 b->sda(b->ctx,true);
 if(!delay(b))return false;
 if(!b->read_sda(b->ctx)){b->status=SI_DATA_HELD;return false;}
 return true;
}
bool si_write(si_bus*b,uint8_t addr,const uint8_t*data,size_t n){
 if(!b)return false;
 b->status=SI_ARGUMENT;
 if(!data||!n||n>17||addr>127||!b->scl||!b->sda||!b->read_scl||!b->read_sda||!b->ticks)return false;
 b->status=SI_OK;b->polls=0;b->started=b->ticks(b->ctx);
 b->sda(b->ctx,true);b->scl(b->ctx,true);
 if(!delay(b))goto failed;
 if(!b->read_scl(b->ctx)||!b->read_sda(b->ctx)){b->status=SI_BUSY;goto failed;}
 b->sda(b->ctx,false);if(!delay(b))goto failed;b->scl(b->ctx,false);
 if(!byte(b,(uint8_t)(addr<<1)))goto failed;
 for(size_t i=0;i<n;i++)if(!byte(b,data[i]))goto failed;
 if(!stop(b))goto failed;
 return true;
failed:
 if(b->status==SI_NACK){si_status cause=b->status;(void)stop(b);b->status=cause;}
 b->scl(b->ctx,true);b->sda(b->ctx,true);return false;
}
