#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. */
    return r->last_header ? DRV_DEVICE_FAULT : DRV_OK;
}
static drv8316_result read_status(const drv8316_hal *h, drv8316_report *r) {
    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]) 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;
    return data == val ? 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);
    if (s != DRV_OK) return stop(h, r, s);
    static const uint8_t settings[][2] = {
        {0x03, 0x03}, /* unlock */
        {0x04, 0x42}, /* preserve reserved bit6; open-drain SDO; 6x PWM cycle-by-cycle current limit */
        {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);
    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);
    return s == DRV_OK ? s : stop(h, r, s);
}
