#include "pca9956b_register_driver.h"

#include <string.h>

enum {
    REG_MODE1 = 0x00,
    REG_MODE2 = 0x01,
    REG_LEDOUT0 = 0x02,
    REG_PWM0 = 0x0A,
    REG_IREF0 = 0x22,
    REG_IREFALL = 0x40,
    REG_PWMALL = 0x3F,
    MODE1_AUTOINC_PWM = 0x20,
    MODE1_AUTOINC_IREF = 0x40,
    MODE2_CHANGE_ON_STOP = 0x00,
    LEDOUT_INDIVIDUAL_PWM = 0xAA,
    CONTROL_PWM0_AUTOINC_PWM = 0x8A,
    CONTROL_IREF0_AUTOINC_ALL = 0xA2
};

static bool callbacks_valid(const pca9956b_io *io) {
    return io && io->write_transaction && io->oe_n_is_high &&
           io->led_rail_is_disabled && io->force_outputs_off;
}

static bool hardware_off(const pca9956b_driver *d) {
    return d->io.oe_n_is_high(d->io.context) &&
           d->io.led_rail_is_disabled(d->io.context);
}

static bool write_bytes(pca9956b_driver *d, uint8_t chip,
                        const uint8_t *bytes, size_t length) {
    if (chip >= d->chip_count || !bytes || length == 0u ||
        length > PCA9956B_I2C_MAX_WRITE_BYTES)
        return false;
    return d->io.write_transaction(d->io.context, d->address_7bit[chip],
                                   bytes, length);
}

static bool write_register(pca9956b_driver *d, uint8_t chip,
                           uint8_t reg, uint8_t value) {
    const uint8_t bytes[2] = {reg, value};
    return write_bytes(d, chip, bytes, sizeof(bytes));
}

static bool write_all_registers(pca9956b_driver *d, uint8_t chip,
                                uint8_t first, uint8_t value, uint8_t count) {
    bool ok = true;
    for (uint8_t i = 0u; i < count; ++i) {
        if (!write_register(d, chip, (uint8_t)(first + i), value)) ok = false;
    }
    return ok;
}

/* Best effort only; the independent OE_N and power gate are the primary fail-off. */
static void make_chip_dark_best_effort(pca9956b_driver *d, uint8_t chip) {
    (void)write_register(d, chip, REG_MODE1, 0x00u);
    (void)write_all_registers(d, chip, REG_LEDOUT0, 0x00u, 6u);
    (void)write_register(d, chip, REG_PWMALL, 0x00u);
    (void)write_register(d, chip, REG_IREFALL, 0x00u);
    (void)write_register(d, chip, REG_MODE2, MODE2_CHANGE_ON_STOP);
}

static void latch_fault_and_force_off(pca9956b_driver *d) {
    if (!d) return;
    d->fault_latched = true;
    d->current_profile_loaded = false;
    bool commanded_off = d->io.force_outputs_off(d->io.context);
    if (commanded_off && hardware_off(d)) {
        for (uint8_t chip = 0u; chip < d->chip_count; ++chip)
            make_chip_dark_best_effort(d, chip);
    }
}

static bool initialize_chip(pca9956b_driver *d, uint8_t chip) {
    if (!write_register(d, chip, REG_MODE1, 0x00u)) return false;
    if (!write_register(d, chip, REG_MODE2, MODE2_CHANGE_ON_STOP)) return false;
    if (!write_all_registers(d, chip, REG_LEDOUT0, 0x00u, 6u)) return false;
    if (!write_register(d, chip, REG_PWMALL, 0x00u)) return false;
    if (!write_register(d, chip, REG_IREFALL, 0x00u)) return false;
    if (!write_all_registers(d, chip, REG_LEDOUT0,
                             LEDOUT_INDIVIDUAL_PWM, 6u))
        return false;
    return write_register(d, chip, REG_MODE1, MODE1_AUTOINC_PWM);
}

static bool addresses_valid(const uint8_t *addresses, size_t count) {
    if (!addresses || count == 0u || count > PCA9956B_MAX_CHIPS) return false;
    for (size_t i = 0u; i < count; ++i) {
        /* Exclude reserved 7-bit I2C address ranges; all candidate straps are in range. */
        if (addresses[i] < 0x08u || addresses[i] > 0x77u) return false;
        for (size_t j = 0u; j < i; ++j) {
            if (addresses[i] == addresses[j]) return false;
        }
    }
    return true;
}

bool pca9956b_init(pca9956b_driver *d, const pca9956b_io *io,
                   const uint8_t *addresses_7bit, size_t chip_count) {
    if (!d) return false;
    memset(d, 0, sizeof(*d));
    if (!callbacks_valid(io) || !addresses_valid(addresses_7bit, chip_count))
        return false;

    d->io = *io;
    d->chip_count = (uint8_t)chip_count;
    memcpy(d->address_7bit, addresses_7bit, chip_count);
    if (!hardware_off(d)) {
        latch_fault_and_force_off(d);
        return false;
    }
    for (uint8_t chip = 0u; chip < d->chip_count; ++chip) {
        if (!initialize_chip(d, chip)) {
            latch_fault_and_force_off(d);
            return false;
        }
    }
    d->initialized = true;
    return true;
}

bool pca9956b_apply_current_profile(pca9956b_driver *d,
                                    const uint8_t *iref_values,
                                    size_t values_length) {
    if (!d || !d->initialized || d->fault_latched || !iref_values ||
        values_length != (size_t)d->chip_count * PCA9956B_CHANNELS_PER_CHIP)
        return false;
    if (!hardware_off(d)) {
        latch_fault_and_force_off(d);
        return false;
    }

    d->current_profile_loaded = false;
    for (uint8_t chip = 0u; chip < d->chip_count; ++chip) {
        uint8_t frame[PCA9956B_CHANNELS_PER_CHIP + 1u];
        frame[0] = CONTROL_IREF0_AUTOINC_ALL;
        memcpy(&frame[1],
               &iref_values[(size_t)chip * PCA9956B_CHANNELS_PER_CHIP],
               PCA9956B_CHANNELS_PER_CHIP);
        if (!write_register(d, chip, REG_MODE1, MODE1_AUTOINC_IREF) ||
            !write_bytes(d, chip, frame, sizeof(frame)) ||
            !write_register(d, chip, REG_MODE1, MODE1_AUTOINC_PWM)) {
            latch_fault_and_force_off(d);
            return false;
        }
    }
    d->current_profile_loaded = true;
    return true;
}

bool pca9956b_write_chip_pwm(void *context, uint8_t chip_index,
                             const uint8_t pwm[PCA9956B_CHANNELS_PER_CHIP]) {
    pca9956b_driver *d = context;
    if (!d || !d->initialized || d->fault_latched ||
        !d->current_profile_loaded || !pwm || chip_index >= d->chip_count)
        return false;
    if (!d->io.oe_n_is_high(d->io.context)) {
        latch_fault_and_force_off(d);
        return false;
    }

    uint8_t frame[PCA9956B_I2C_MAX_WRITE_BYTES];
    frame[0] = CONTROL_PWM0_AUTOINC_PWM;
    memcpy(&frame[1], pwm, PCA9956B_CHANNELS_PER_CHIP);
    if (!write_bytes(d, chip_index, frame, sizeof(frame))) {
        latch_fault_and_force_off(d);
        return false;
    }
    return true;
}

bool pca9956b_is_ready(const pca9956b_driver *d) {
    return d && d->initialized && d->current_profile_loaded &&
           !d->fault_latched;
}
