#include "vacuum_display.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
typedef struct{uint8_t ram[1024];unsigned page,col,calls,fail_at;bool on,mode_set,off_failed;} model;
static bool bus(void*ctx,uint8_t addr,const uint8_t*b,size_t n){
 model*m=ctx;assert(addr==0x3c||addr==0x3d);assert(n>=2&&n<=17);
 ++m->calls;if(m->calls==m->fail_at)return false;
 if(m->off_failed&&b[0]==0&&b[1]==0xae)return false;
 if(b[0]==0x40){assert(m->mode_set&&!m->on);for(size_t i=1;i<n;i++){assert(m->page<4&&m->col<128);m->ram[m->page*128+m->col++]=b[i];}}
 else{assert(b[0]==0);for(size_t i=1;i<n;i++){
  unsigned v=b[i];if(v==0xae)m->on=false;else if(v==0xaf)m->on=true;
  else if(v>=0xb0&&v<=0xb7)m->page=v-0xb0;
  else if(v<=0xf)m->col=(m->col&0xf0)|v;
  else if(v>=0x10&&v<=0x1f)m->col=(m->col&0x0f)|((v&0xf)<<4);
  else if(v==0x20){assert(++i<n&&b[i]==2);m->mode_set=true;}
  else if(v==0xa8){assert(++i<n&&b[i]==31);}
  else if(v==0xd5||v==0xd3||v==0xda||v==0x81||v==0x8d){assert(++i<n);}
  else assert(v==0x40||v==0xa1||v==0xc8||v==0xa4||v==0xa6);
 }}return true;
}
static uint8_t f[512];static vd_view view;
static void out(const char*name){FILE*fp=fopen(name,"wb");assert(fp);assert(fwrite(f,1,512,fp)==512);fclose(fp);}
int main(void){
 vd_input in={.level=4,.percent=100,.fan_enabled=true,.qualified=true,.measurement_valid=true,.sampled_ms=10,.now_ms=20};
 for(unsigned x=0;x<256;x++){in.percent=(uint8_t)x;vd_render(&in,f,&view);assert(view.percent_visible==(x<=100));}
 in.percent=100;vd_render(&in,f,&view);assert(!strcmp(view.lines[2],"SUCTION 100%"));out("valid100.bin");
 in.percent=37;in.low_suction=true;vd_render(&in,f,&view);assert(view.alarm_visible&&!strcmp(view.lines[3],"CHECK FILTER"));out("low37.bin");
 in.qualified=false;vd_render(&in,f,&view);assert(!view.percent_visible&&!view.alarm_visible);out("unqualified.bin");
 in.qualified=true;in.now_ms=511;vd_render(&in,f,&view);assert(!view.percent_visible);in.now_ms=510;vd_render(&in,f,&view);assert(view.percent_visible);
 in.sampled_ms=0xfffffff0u;in.now_ms=0x10;vd_render(&in,f,&view);assert(view.percent_visible);
 in.now_ms=0xffffffefu;vd_render(&in,f,&view);assert(!view.percent_visible);
 in.sampled_ms=10;in.now_ms=20;in.learning_busy=true;vd_render(&in,f,&view);assert(!view.percent_visible&&!strcmp(view.lines[0],"CALIBRATING"));out("learning.bin");
 in.learning_busy=false;in.restart_required=true;vd_render(&in,f,&view);assert(!view.percent_visible);
 in.restart_required=false;in.fault=true;vd_render(&in,f,&view);assert(!view.percent_visible&&!view.alarm_visible);
 in.fault=false;in.fan_enabled=false;vd_render(&in,f,&view);assert(!view.percent_visible);
 in.fan_enabled=true;in.measurement_valid=false;vd_render(&in,f,&view);assert(!view.percent_visible);
 for(unsigned level=0;level<256;level++){in.level=(uint8_t)level;vd_render(&in,f,&view);assert(view.lines[1][6]==((level>=1&&level<=4)?(char)('0'+level):'-'));}
 in.level=4;
 vd_render(NULL,f,&view);assert(!view.percent_visible);
 in.measurement_valid=true;in.percent=100;vd_render(&in,f,&view);
 for(unsigned addr=0x3c;addr<=0x3d;addr++){
  vd_driver d;model m={0};assert(vd_begin(&d,(uint8_t)addr,f));while(vd_service(&d,bus,&m))assert(m.calls<41);
  assert(!d.failed&&m.on&&m.calls==40&&!memcmp(m.ram,f,512));for(unsigned i=512;i<1024;i++)assert(m.ram[i]==0);
 }
 /* Fail every packet independently: stop progress, never show partial content,
    and report an ACKed off command separately from a failed off attempt. */
 for(unsigned fail=1;fail<=40;fail++)for(unsigned off_fail=0;off_fail<2;off_fail++){
  vd_driver d;model m={.fail_at=fail,.off_failed=off_fail};assert(vd_begin(&d,0x3c,f));while(vd_service(&d,bus,&m))assert(m.calls<42);
  assert(d.failed&&d.display_off_confirmed==!off_fail);assert(!vd_service(&d,bus,&m));
 }
 vd_driver d;assert(!vd_begin(&d,0x78,f));assert(!vd_begin(&d,0x3c,NULL));assert(vd_begin(&d,0x3c,f));assert(!vd_service(&d,NULL,NULL)&&d.failed);
 puts("PASS display: all256 percentages, freshness/wrap/qualification, four status frames; independent GDDRAM model and80 transfer failures");
}
