#include "drv8316.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>

typedef struct {
    uint8_t regs[64];
    unsigned calls, fail_at, corrupt_at, fault_at, enable_calls;
    uint32_t now_us, wake_at;
    bool off, awake;
} mock;
static void off(void *ctx, bool value) {
    mock *m=ctx; m->off=value; if (!value) ++m->enable_calls;
}
static void awake(void *ctx, bool value) {
    mock *m=ctx; m->awake=value;
    if (!value) { memset(m->regs,0,sizeof(m->regs)); m->regs[4]=0x60; m->regs[5]=0x46; m->regs[6]=0x10; }
    else m->wake_at=m->now_us;
}
static void delay(void *ctx,uint32_t us) { ((mock *)ctx)->now_us+=us; }
static bool transfer(void *ctx,uint16_t tx,uint16_t *rx) {
    mock *m=ctx; ++m->calls;
    assert(m->awake && m->off && m->now_us-m->wake_at>=1000);
    unsigned ones=0;for(unsigned i=0;i<16;++i) ones+=(tx>>i)&1u;
    assert(ones%2u==0);
    if(m->calls==m->fail_at) return false;
    unsigned addr=(tx>>9)&63u;uint8_t previous=m->regs[addr];
    if(!(tx&0x8000u)) {
        if(addr==3 || m->regs[3]==3) m->regs[addr]=(uint8_t)tx;
    }
    *rx=previous; /* write response intentionally OLD value */
    if(m->calls==m->corrupt_at) *rx^=1u;
    if(m->calls==m->fault_at) *rx|=0x8000u;
    return true;
}
static drv8316_hal hal(mock *m) { return (drv8316_hal){m,transfer,off,awake,delay}; }
int main(void) {
    uint16_t w;
    assert(drv8316_frame(true,0,0,&w) && w==0x8100);
    assert(drv8316_frame(true,4,0,&w) && w==0x8800);
    assert(drv8316_frame(false,3,3,&w) && w==0x0603);
    assert(drv8316_frame(false,4,0x40,&w) && w==0x0840);
    assert(!drv8316_frame(false,64,0,&w));
    assert(!drv8316_frame(false,0,0,NULL));
    for(unsigned r=0;r<2;++r) for(unsigned a=0;a<64;++a) for(unsigned d=0;d<256;++d) {
        assert(drv8316_frame(r!=0,(uint8_t)a,(uint8_t)d,&w));
        assert((w>>15)==r && ((w>>9)&63u)==a && (w&255u)==d);
        unsigned n=0;for(unsigned k=0;k<16;++k) n+=(w>>k)&1u;
        assert(n%2u==0);
    }
    mock m={0};drv8316_hal h=hal(&m);drv8316_report report;
    assert(drv8316_configure_off(&h,&report)==DRV_OK);
    assert(report.configured && m.off && m.awake && m.enable_calls==0);
    assert(m.regs[3]==6 && m.regs[4]==0x42 && m.regs[5]==0x4f && m.regs[6]==0x10 && m.regs[7]==1 && m.regs[8]==1);
    unsigned count=m.calls;
    for(unsigned i=1;i<=count;++i) {
        mock f={.fail_at=i};drv8316_hal fh=hal(&f);
        assert(drv8316_configure_off(&fh,&report)==DRV_IO);
        assert(!report.configured && f.off && !f.awake && f.enable_calls==0);
        mock e={.fault_at=i};drv8316_hal eh=hal(&e);
        assert(drv8316_configure_off(&eh,&report)==DRV_DEVICE_FAULT);
        assert(!report.configured && e.off && !e.awake);
    }
    for(unsigned i=5;i<=19;i+=2) { /* each config readback */
        mock f={.corrupt_at=i};drv8316_hal fh=hal(&f);
        assert(drv8316_configure_off(&fh,&report)==DRV_READBACK);
        assert(!report.configured && f.off && !f.awake);
    }
    for(unsigned addr=0;addr<3;++addr) {
        mock f={0};drv8316_hal fh=hal(&f);
        assert(drv8316_configure_off(&fh,&report)==DRV_OK);
        f.regs[addr]=1;
        assert(drv8316_poll_faults(&fh,&report)==DRV_DEVICE_FAULT);
        assert(!report.configured && f.off && !f.awake);
    }
    mock z={0};drv8316_hal zh=hal(&z);
    assert(drv8316_configure_off(&zh,&report)==DRV_OK);
    assert(drv8316_poll_faults(&zh,&report)==DRV_OK);
    zh.transfer16=NULL;
    assert(drv8316_poll_faults(&zh,&report)==DRV_INVALID && z.off && !z.awake);
    assert(drv8316_configure_off(NULL,&report)==DRV_INVALID);
    printf("PASS: 32768 SPI frames; %u init transfers fault-injected; 8 readback mismatches; 3 fault registers; output never enabled.\n",count);
}
