#include "drv8316.h"
#include <stddef.h>
#include <string.h>

bool drv8316_frame(bool read, uint8_t address, uint8_t data, uint16_t *frame) {
    if (frame == NULL || address > 63u) return false;
    uint16_t w = (uint16_t)(((uint16_t)address << 9) | data);
    if (read) w |= UINT16_C(0x8000);
    unsigned parity = 0;
    for (unsigned i = 0; i < 16u; ++i) parity ^= (w >> i) & 1u;
    *frame = (uint16_t)(w | (uint16_t)(parity << 8));
    return true;
}
static bool valid(const drv8316_hal *h) {
    return h && h->transfer16 && h->set_off && h->set_awake && h->delay_us;
}
static drv8316_result stop(const drv8316_hal *h, drv8316_report *r,
                          drv8316_result reason) {
    if (h && h->set_off) h->set_off(h->ctx, true);
    if (h && h->set_awake) h->set_awake(h->ctx, false);
    if (r) r->configured = false;
    return reason;
}
static drv8316_result xfer(const drv8316_hal *h, drv8316_report *r,
                          bool read, uint8_t addr, uint8_t value, uint8_t *data) {
    uint16_t tx = 0, rx = 0;
    if (!drv8316_frame(read, addr, value, &tx)) return DRV_INVALID;
    r->last_address = addr;
    if (!h->transfer16(h->ctx, tx, &rx)) return DRV_IO;
    h->delay_us(h->ctx, 1);
    r->last_header = (uint8_t)(rx >> 8);
    *data = (uint8_t)rx;
    /* SDO header is status, not an echoed address/parity field. */
    /* IC_STAT bit3 NPOR=1 means no power reset, not a fault.
     * Reject every other bit, including unexpected reserved bit7. */
    if (r->configured && !(r->last_header & 0x08u))
        return DRV_DEVICE_FAULT; /* do not trust configuration across a new VM reset */
    return (r->last_header & 0xf7u) ? DRV_DEVICE_FAULT : DRV_OK;
}
static drv8316_result read_status(const drv8316_hal *h, drv8316_report *r,
                                  bool require_no_por) {
    for (uint8_t i = 0; i < 3u; ++i) {
        drv8316_result s = xfer(h, r, true, i, 0, &r->status[i]);
        if (s != DRV_OK) return s;
        if (r->status[i] & (i == 0u ? 0xf7u : 0xffu))
            return DRV_DEVICE_FAULT;
    }
    /* Startup alone permits the reset-latched zero. After CLR_FLT,
     * NPOR must be one in both the data and each status header. */
    if (require_no_por && (!(r->status[0] & 0x08u) ||
                           !(r->last_header & 0x08u)))
        return DRV_DEVICE_FAULT;
    return DRV_OK;
}
static drv8316_result write_check(const drv8316_hal *h, drv8316_report *r,
                                 uint8_t addr, uint8_t val) {
    uint8_t data = 0;
    drv8316_result s = xfer(h, r, false, addr, val, &data);
    if (s != DRV_OK) return s;
    /* Write response contains the OLD value. Verify with a new read. */
    s = xfer(h, r, true, addr, 0, &data);
    if (s != DRV_OK) return s;
    /* Control2 CLR_FLT is W1C and self-clears; it is not read back as one. */
    uint8_t expected = addr == 0x04u ? (uint8_t)(val & 0xfeu) : val;
    return data == expected ? DRV_OK : DRV_READBACK;
}
drv8316_result drv8316_configure_off(const drv8316_hal *h, drv8316_report *r) {
    if (!valid(h) || !r) return stop(h, r, DRV_INVALID);
    h->set_off(h->ctx, true);
    memset(r, 0, sizeof(*r));
    h->set_awake(h->ctx, false);
    h->delay_us(h->ctx, 1000); /* enter sleep; reset device configuration */
    h->set_awake(h->ctx, true);
    h->delay_us(h->ctx, 2000); /* margin over datasheet wake time */
    drv8316_result s = read_status(h, r, false);
    if (s != DRV_OK) return stop(h, r, s);
    static const uint8_t settings[][2] = {
        {0x03, 0x03}, /* unlock */
        {0x04, 0x43}, /* reserved bit6; open-drain SDO; 6x CBC; W1C CLR_FLT acknowledges startup NPOR */
        {0x05, 0x4f}, /* reserved bits preserved; 22V OVP + thermal warning */
        {0x06, 0x10}, /* latch OCP; no auto retry; 0.6us deglitch */
        {0x07, 0x01}, /* 0.3 V/A current gain; AAR/ASR disabled */
        {0x08, 0x01}, /* disable unused buck, external network still required */
        {0x0c, 0x00}, /* no uncalibrated delay compensation */
        {0x03, 0x06}  /* lock configuration */
    };
    for (unsigned i = 0; i < sizeof(settings)/sizeof(settings[0]); ++i) {
        s = write_check(h, r, settings[i][0], settings[i][1]);
        if (s != DRV_OK) return stop(h, r, s);
    }
    s = read_status(h, r, true);
    if (s != DRV_OK) return stop(h, r, s);
    r->configured = true;
    return DRV_OK; /* DRVOFF remains asserted. */
}
drv8316_result drv8316_poll_faults(const drv8316_hal *h, drv8316_report *r) {
    if (!valid(h) || !r || !r->configured) return stop(h, r, DRV_INVALID);
    drv8316_result s = read_status(h, r, true);
    return s == DRV_OK ? s : stop(h, r, s);
}
