#include "charge_registers.h"

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

typedef struct {
    uint8_t regs[128][256];
    bool ce_enabled;
    int fail_read_at;
    int fail_write_at;
    int reads;
    int writes;
    uint32_t now_ms;
} mock;

static bool bus_read(void *ctx, uint8_t address, uint8_t reg,
                     uint8_t *data, uint8_t length) {
    mock *m = ctx;
    if (++m->reads == m->fail_read_at) return false;
    if (data == NULL || (unsigned)reg + length > 256u) return false;
    memcpy(data, &m->regs[address][reg], length);
    return true;
}

static bool bus_write(void *ctx, uint8_t address, uint8_t reg,
                      const uint8_t *data, uint8_t length) {
    mock *m = ctx;
    if (++m->writes == m->fail_write_at) return false;
    if (data == NULL || (unsigned)reg + length > 256u) return false;
    memcpy(&m->regs[address][reg], data, length);
    if (address == CHARGE_BQ25798_ADDR && reg == 0x10u && length == 1u)
        m->regs[address][reg] &= (uint8_t)~0x08u; /* WD_RST self clears. */
    return true;
}

static bool set_ce(void *ctx, bool enabled) {
    ((mock *)ctx)->ce_enabled = enabled;
    return true;
}

static uint32_t millis(void *ctx) { return ((mock *)ctx)->now_ms; }

static charge_register_io make_io(mock *m) {
    return (charge_register_io){m, bus_read, bus_write, set_ce, millis};
}

static void setup_source(mock *m) {
    m->regs[CHARGE_HUSB238_ADDR][0x00] = 0x26; /* PD 9V, 2A */
    m->regs[CHARGE_HUSB238_ADDR][0x01] = 0x40; /* attached */
    m->regs[CHARGE_HUSB238_ADDR][0x02] = 0x00;
    m->regs[CHARGE_BQ25798_ADDR][0x09] = 0x05;
    m->regs[CHARGE_BQ25798_ADDR][0x10] = 0x85;
    m->regs[CHARGE_BQ25798_ADDR][0x14] = 0x16;
    m->regs[CHARGE_BQ25798_ADDR][0x18] = 0x54; /* TS_IGNORE=0 */
    m->regs[CHARGE_BQ25798_ADDR][0x1B] = 0x09; /* PG + VBUS present */
    m->regs[CHARGE_BQ25798_ADDR][0x1D] = 0x01; /* battery present */
    m->regs[CHARGE_BQ25798_ADDR][0x35] = 0x23; /* 9000mV */
    m->regs[CHARGE_BQ25798_ADDR][0x36] = 0x28;
}

int main(void) {
    mock m = {0};
    setup_source(&m);
    charge_register_io io = make_io(&m);
    charge_registers device;
    assert(charge_registers_init(&device, &io));
    assert(!m.ce_enabled);
    assert((m.regs[CHARGE_BQ25798_ADDR][0x09] & 0x20u) != 0u);
    assert((m.regs[CHARGE_BQ25798_ADDR][0x10] & 0x07u) == 5u);
    assert((m.regs[CHARGE_BQ25798_ADDR][0x2E] & 0xF0u) == 0x80u);

    charge_observation o;
    assert(charge_registers_observe(&device, &o));
    assert(o.source_read_ok && o.charger_read_ok && o.pack_and_ntc_valid);
    assert(o.charger_fault_free && o.vbus_mv == 9000u);
    assert(o.pd_status0 == 0x26u && o.pd_status1 == 0x40u);

    charge_hal h = charge_registers_make_hal(&device);
    charge_report report = {0};
    assert(charge_manager_step(&h, &o, &report) == CHARGE_ACTIVE);
    assert(m.ce_enabled && report.active);
    /* IINDPM 1800mA -> 0x00B4; ICHG 1000mA -> 0x0064, MSB first. */
    assert(m.regs[CHARGE_BQ25798_ADDR][0x06] == 0x00u);
    assert(m.regs[CHARGE_BQ25798_ADDR][0x07] == 0xB4u);
    assert(m.regs[CHARGE_BQ25798_ADDR][0x03] == 0x00u);
    assert(m.regs[CHARGE_BQ25798_ADDR][0x04] == 0x64u);
    assert((m.regs[CHARGE_BQ25798_ADDR][0x14] & 0x02u) == 0u);

    assert(charge_registers_kick_watchdog(&device));
    m.regs[CHARGE_BQ25798_ADDR][0x1F] = 0x04; /* TS warm: conservative block */
    assert(charge_registers_observe(&device, &o));
    assert(!o.pack_and_ntc_valid);
    assert(charge_manager_step(&h, &o, &report) == CHARGE_PACK_OR_TEMPERATURE_FAULT);
    assert(!m.ce_enabled);

    /* Bus write and verification failures in each charger-control register
     * callback must stop the state machine with CE high. */
    for (int fail = 1; fail <= 4; ++fail) {
        mock f = {0}; setup_source(&f);
        charge_register_io fi = make_io(&f); charge_registers fd;
        assert(charge_registers_init(&fd, &fi));
        charge_observation fo;
        assert(charge_registers_observe(&fd, &fo));
        charge_hal fh = charge_registers_make_hal(&fd);
        charge_report fr = {0};
        f.fail_write_at = f.writes + fail;
        assert(charge_manager_step(&fh, &fo, &fr) == CHARGE_IO_ERROR);
        assert(!fr.active && !f.ce_enabled);
    }

    for (int fail = 1; fail <= 3; ++fail) {
        mock f = {0}; setup_source(&f); f.fail_write_at = fail;
        charge_register_io fi = make_io(&f); charge_registers fd;
        assert(!charge_registers_init(&fd, &fi));
        assert(!f.ce_enabled);
    }
    mock failed_read = {0}; setup_source(&failed_read);
    charge_register_io ri = make_io(&failed_read); charge_registers rd;
    assert(charge_registers_init(&rd, &ri));
    failed_read.fail_read_at = failed_read.reads + 1;
    assert(!charge_registers_observe(&rd, &o));
    assert(!o.source_read_ok && o.charger_read_ok);

    printf("PASS: BQ/HUSB register mapping, ADC/watchdog setup, VBUS/status decoding, readback, and four injected charger-register write failures.\n");
    return 0;
}
