#include "is31fl3236a.h"

#include <string.h>

enum {
    REG_SHUTDOWN = 0x00u,
    REG_PWM_FIRST = 0x01u,
    REG_PWM_UPDATE = 0x25u,
    REG_CHANNEL_CONTROL_FIRST = 0x26u,
    REG_GLOBAL_CONTROL = 0x4Au,
    REG_FREQUENCY = 0x4Bu,
    REG_RESET = 0x4Fu,
    SHUTDOWN_SOFTWARE = 0x00u,
    SHUTDOWN_NORMAL = 0x01u,
    GLOBAL_ENABLE = 0x00u,
    GLOBAL_SHUTDOWN = 0x01u,
    REGISTER_RESET = 0x00u,
    CHANNEL_CONTROL_MASK = 0x07u
};

static bool io_valid(const is31fl3236a_io *io) {
    return io && io->write_transaction && io->sdb_is_low &&
           io->driver_supply_is_valid && io->output_permit_is_active &&
           io->set_sdb_low && io->force_outputs_off;
}

static bool address_valid(uint8_t address) {
    return address >= 0x08u && address <= 0x77u;
}

uint8_t is31fl3236a_address_from_ad(uint8_t ad_code) {
    return ad_code <= 3u ? (uint8_t)(0x3Cu + ad_code) : 0xFFu;
}

static bool hardware_safe_for_i2c(const is31fl3236a_driver *d) {
    return d->io.sdb_is_low(d->io.context) &&
           d->io.driver_supply_is_valid(d->io.context);
}

static bool write_register(is31fl3236a_driver *d, uint8_t reg, uint8_t value) {
    const uint8_t bytes[2] = {reg, value};
    return d->io.write_transaction(d->io.context, d->address_7bit,
                                   bytes, sizeof(bytes));
}

static bool write_bank(is31fl3236a_driver *d, uint8_t first_register,
                       const uint8_t values[IS31FL3236A_CHANNELS]) {
    uint8_t bytes[IS31FL3236A_MAX_TRANSACTION_BYTES];
    bytes[0] = first_register;
    memcpy(&bytes[1], values, IS31FL3236A_CHANNELS);
    return d->io.write_transaction(d->io.context, d->address_7bit,
                                   bytes, sizeof(bytes));
}

static void force_off_and_latch_fault(is31fl3236a_driver *d) {
    if (!d) return;
    d->fault_latched = true;
    d->frame_loaded = false;
    d->outputs_enabled = false;
    (void)d->io.force_outputs_off(d->io.context, true);
    if (d->io.sdb_is_low(d->io.context) &&
        d->io.driver_supply_is_valid(d->io.context)) {
        (void)write_register(d, REG_SHUTDOWN, SHUTDOWN_SOFTWARE);
        (void)write_register(d, REG_GLOBAL_CONTROL, GLOBAL_SHUTDOWN);
    }
}

bool is31fl3236a_initialize(is31fl3236a_driver *d,
                            const is31fl3236a_io *io,
                            uint8_t address_7bit,
                            is31fl3236a_pwm_frequency pwm_frequency) {
    if (!d) return false;
    memset(d, 0, sizeof(*d));
    if (!io_valid(io) || !address_valid(address_7bit) ||
        (pwm_frequency != IS31FL3236A_PWM_3KHZ &&
         pwm_frequency != IS31FL3236A_PWM_22KHZ))
        return false;

    d->io = *io;
    d->address_7bit = address_7bit;
    d->pwm_frequency = pwm_frequency;
    if (!hardware_safe_for_i2c(d)) {
        force_off_and_latch_fault(d);
        return false;
    }

    uint8_t zeros[IS31FL3236A_CHANNELS] = {0};
    if (!write_register(d, REG_RESET, REGISTER_RESET) ||
        !write_register(d, REG_SHUTDOWN, SHUTDOWN_SOFTWARE) ||
        !write_register(d, REG_GLOBAL_CONTROL, GLOBAL_SHUTDOWN) ||
        !write_register(d, REG_FREQUENCY, (uint8_t)pwm_frequency) ||
        !write_bank(d, REG_PWM_FIRST, zeros) ||
        !write_bank(d, REG_CHANNEL_CONTROL_FIRST, zeros) ||
        !write_register(d, REG_PWM_UPDATE, 0x00u)) {
        force_off_and_latch_fault(d);
        return false;
    }
    d->initialized = true;
    return true;
}

bool is31fl3236a_write_frame(is31fl3236a_driver *d,
                             const uint8_t pwm[IS31FL3236A_CHANNELS],
                             const uint8_t channel_control[IS31FL3236A_CHANNELS]) {
    if (!d || !d->initialized || d->fault_latched) return false;
    if (d->outputs_enabled) return false;
    /* Never allow a rejected replacement frame to leave an old frame armed. */
    d->frame_loaded = false;
    if (!pwm || !channel_control) return false;
    if (!hardware_safe_for_i2c(d)) {
        force_off_and_latch_fault(d);
        return false;
    }
    for (size_t i = 0u; i < IS31FL3236A_CHANNELS; ++i) {
        if ((channel_control[i] & (uint8_t)~CHANNEL_CONTROL_MASK) != 0u)
            return false;
    }
    if (!write_bank(d, REG_PWM_FIRST, pwm) ||
        !write_bank(d, REG_CHANNEL_CONTROL_FIRST, channel_control) ||
        !write_register(d, REG_PWM_UPDATE, 0x00u)) {
        force_off_and_latch_fault(d);
        return false;
    }
    d->frame_loaded = true;
    return true;
}

bool is31fl3236a_enable_outputs(is31fl3236a_driver *d) {
    if (!d || !d->initialized || d->fault_latched || !d->frame_loaded)
        return false;
    if (!hardware_safe_for_i2c(d) ||
        !d->io.output_permit_is_active(d->io.context))
        return false;
    if (!write_register(d, REG_GLOBAL_CONTROL, GLOBAL_ENABLE) ||
        !write_register(d, REG_SHUTDOWN, SHUTDOWN_NORMAL)) {
        force_off_and_latch_fault(d);
        return false;
    }
    if (!d->io.set_sdb_low(d->io.context, false) ||
        d->io.sdb_is_low(d->io.context)) {
        force_off_and_latch_fault(d);
        return false;
    }
    d->outputs_enabled = true;
    return true;
}

bool is31fl3236a_disable_outputs(is31fl3236a_driver *d) {
    if (!d || !d->initialized || d->fault_latched) return false;
    if (!d->io.set_sdb_low(d->io.context, true) ||
        !d->io.sdb_is_low(d->io.context)) {
        force_off_and_latch_fault(d);
        return false;
    }
    d->outputs_enabled = false;
    if (!d->io.driver_supply_is_valid(d->io.context)) return true;
    if (!write_register(d, REG_SHUTDOWN, SHUTDOWN_SOFTWARE) ||
        !write_register(d, REG_GLOBAL_CONTROL, GLOBAL_SHUTDOWN)) {
        force_off_and_latch_fault(d);
        return false;
    }
    return true;
}

static bool same_group_io(const is31fl3236a_io *a, const is31fl3236a_io *b) {
    return a->context == b->context &&
           a->write_transaction == b->write_transaction &&
           a->sdb_is_low == b->sdb_is_low &&
           a->driver_supply_is_valid == b->driver_supply_is_valid &&
           a->output_permit_is_active == b->output_permit_is_active &&
           a->set_sdb_low == b->set_sdb_low &&
           a->force_outputs_off == b->force_outputs_off;
}

static bool group_members_valid(is31fl3236a_driver *drivers, size_t count,
                                bool require_frame, bool require_outputs_off) {
    if (!drivers || count == 0u || count > IS31FL3236A_MAX_GROUP_CHIPS)
        return false;
    is31fl3236a_driver *first = &drivers[0];
    if (!first->initialized || first->fault_latched ||
        (require_frame && !first->frame_loaded) ||
        (require_outputs_off && first->outputs_enabled) ||
        !io_valid(&first->io))
        return false;
    for (size_t i = 1u; i < count; ++i) {
        is31fl3236a_driver *d = &drivers[i];
        if (!d->initialized || d->fault_latched ||
            (require_frame && !d->frame_loaded) ||
            (require_outputs_off && d->outputs_enabled) ||
            !io_valid(&d->io) || !same_group_io(&first->io, &d->io) ||
            d->pwm_frequency != first->pwm_frequency)
            return false;
        for (size_t j = 0u; j < i; ++j) {
            if (d->address_7bit == drivers[j].address_7bit) return false;
        }
    }
    return true;
}

static void group_latch_fault(is31fl3236a_driver *drivers, size_t count) {
    if (!drivers || count == 0u || count > IS31FL3236A_MAX_GROUP_CHIPS ||
        !io_valid(&drivers[0].io))
        return;
    is31fl3236a_io *io = &drivers[0].io;
    (void)io->set_sdb_low(io->context, true);
    (void)io->force_outputs_off(io->context, true);
    for (size_t i = 0u; i < count; ++i) {
        drivers[i].outputs_enabled = false;
        drivers[i].frame_loaded = false;
        drivers[i].fault_latched = true;
    }
}

bool is31fl3236a_enable_group(is31fl3236a_driver *drivers, size_t count) {
    if (!group_members_valid(drivers, count, true, true)) return false;
    is31fl3236a_driver *first = &drivers[0];
    if (!hardware_safe_for_i2c(first) ||
        !first->io.output_permit_is_active(first->io.context))
        return false;

    /* Program every chip while common hardware shutdown still masks outputs. */
    for (size_t i = 0u; i < count; ++i) {
        if (!write_register(&drivers[i], REG_GLOBAL_CONTROL, GLOBAL_ENABLE) ||
            !write_register(&drivers[i], REG_SHUTDOWN, SHUTDOWN_NORMAL)) {
            group_latch_fault(drivers, count);
            return false;
        }
    }

    /* A permit or supply transition during I2C must keep the whole group dark. */
    if (!hardware_safe_for_i2c(first) ||
        !first->io.output_permit_is_active(first->io.context)) {
        group_latch_fault(drivers, count);
        return false;
    }
    if (!first->io.set_sdb_low(first->io.context, false) ||
        first->io.sdb_is_low(first->io.context)) {
        group_latch_fault(drivers, count);
        return false;
    }
    for (size_t i = 0u; i < count; ++i) drivers[i].outputs_enabled = true;
    return true;
}

bool is31fl3236a_disable_group(is31fl3236a_driver *drivers, size_t count) {
    if (!group_members_valid(drivers, count, false, false)) return false;
    is31fl3236a_driver *first = &drivers[0];
    if (!first->io.set_sdb_low(first->io.context, true) ||
        !first->io.sdb_is_low(first->io.context)) {
        group_latch_fault(drivers, count);
        return false;
    }
    for (size_t i = 0u; i < count; ++i) drivers[i].outputs_enabled = false;
    if (!first->io.driver_supply_is_valid(first->io.context)) return true;
    for (size_t i = 0u; i < count; ++i) {
        if (!write_register(&drivers[i], REG_SHUTDOWN, SHUTDOWN_SOFTWARE) ||
            !write_register(&drivers[i], REG_GLOBAL_CONTROL, GLOBAL_SHUTDOWN)) {
            group_latch_fault(drivers, count);
            return false;
        }
    }
    return true;
}

bool is31fl3236a_stage_group_frame(
    is31fl3236a_driver *drivers, size_t count,
    const uint8_t pwm[][IS31FL3236A_CHANNELS],
    const uint8_t channel_control[][IS31FL3236A_CHANNELS]) {
    if (!pwm || !channel_control ||
        !group_members_valid(drivers, count, false, true))
        return false;
    is31fl3236a_driver *first = &drivers[0];
    if (!hardware_safe_for_i2c(first) ||
        !first->io.sdb_is_low(first->io.context)) {
        group_latch_fault(drivers, count);
        return false;
    }
    if (!first->io.output_permit_is_active(first->io.context)) {
        for (size_t i = 0u; i < count; ++i) drivers[i].frame_loaded = false;
        return false;
    }
    for (size_t i = 0u; i < count; ++i) {
        if (!hardware_safe_for_i2c(first)) {
            group_latch_fault(drivers, count);
            return false;
        }
        if (!first->io.output_permit_is_active(first->io.context)) {
            for (size_t j = 0u; j < count; ++j) drivers[j].frame_loaded = false;
            return false;
        }
        if (!is31fl3236a_write_frame(&drivers[i], pwm[i], channel_control[i])) {
            group_latch_fault(drivers, count);
            return false;
        }
    }
    if (!hardware_safe_for_i2c(first)) {
        group_latch_fault(drivers, count);
        return false;
    }
    if (!first->io.output_permit_is_active(first->io.context)) {
        for (size_t i = 0u; i < count; ++i) drivers[i].frame_loaded = false;
        return false;
    }
    return true;
}

bool is31fl3236a_update_group(
    is31fl3236a_driver *drivers, size_t count,
    const uint8_t pwm[][IS31FL3236A_CHANNELS],
    const uint8_t channel_control[][IS31FL3236A_CHANNELS]) {
    if (!pwm || !channel_control ||
        !group_members_valid(drivers, count, false, false))
        return false;

    bool any_enabled = false;
    bool all_enabled = true;
    for (size_t i = 0u; i < count; ++i) {
        any_enabled = any_enabled || drivers[i].outputs_enabled;
        all_enabled = all_enabled && drivers[i].outputs_enabled;
    }
    if (any_enabled && !all_enabled) {
        group_latch_fault(drivers, count);
        return false;
    }
    if (any_enabled) {
        if (!is31fl3236a_disable_group(drivers, count)) return false;
    } else if (!drivers[0].io.sdb_is_low(drivers[0].io.context)) {
        group_latch_fault(drivers, count);
        return false;
    }

    if (!is31fl3236a_stage_group_frame(drivers, count, pwm, channel_control))
        return false;
    if (is31fl3236a_enable_group(drivers, count)) return true;
    /* A normal permit withdrawal is not a hardware fault, but must not leave
       an old staged frame eligible for a later enable call. */
    for (size_t i = 0u; i < count; ++i) {
        if (!drivers[i].fault_latched) drivers[i].frame_loaded = false;
    }
    return false;
}

bool is31fl3236a_is_ready(const is31fl3236a_driver *d) {
    return d && d->initialized && d->frame_loaded && !d->fault_latched;
}
