#include "is31fl3236a_lamp_backend.h"

#include <string.h>

enum { LAMP_PROFILE_MASK = 0x07u };

static bool backend_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 backend_set_oe_n(void *context, bool high) {
    is31fl3236a_lamp_backend *b = context;
    if (!b) return false;
    if (!high) {
        if (!b->driver_group_initialized || !b->frame_staged ||
            !b->led_power_requested || !b->led_power_good(b->power_context))
            return false;
        return is31fl3236a_enable_group(b->drivers, b->driver_count);
    }

    if (b->driver_group_initialized) {
        bool any_enabled = false;
        bool all_enabled = true;
        for (uint8_t i = 0u; i < b->driver_count; ++i) {
            any_enabled = any_enabled || b->drivers[i].outputs_enabled;
            all_enabled = all_enabled && b->drivers[i].outputs_enabled;
        }
        if (any_enabled) {
            if (!all_enabled) return false;
            return is31fl3236a_disable_group(b->drivers, b->driver_count);
        }
    }
    return b->driver_io.set_sdb_low(b->driver_io.context, true) &&
           b->driver_io.sdb_is_low(b->driver_io.context);
}

static bool backend_set_power_request(void *context, bool enabled) {
    is31fl3236a_lamp_backend *b = context;
    if (!b || !b->set_led_power_request ||
        !b->set_led_power_request(b->power_context, enabled))
        return false;
    b->led_power_requested = enabled;
    return true;
}

static bool backend_power_good(void *context) {
    is31fl3236a_lamp_backend *b = context;
    return b && b->led_power_good && b->led_power_good(b->power_context);
}

static bool backend_write_chip_pwm(
    void *context, uint8_t block_index,
    const uint8_t pwm[LG_OUTPUT_CHANNELS_PER_CHIP]) {
    is31fl3236a_lamp_backend *b = context;
    if (!b || !pwm || !b->driver_group_initialized ||
        block_index >= b->write_blocks_expected)
        return false;
    if (block_index == 0u) {
        memset(b->staged_pwm, 0, sizeof(b->staged_pwm));
        b->write_blocks_received = 0u;
        b->frame_staged = false;
    }
    if (block_index != b->write_blocks_received) return false;

    size_t first = (size_t)block_index * LG_OUTPUT_CHANNELS_PER_CHIP;
    size_t used = (size_t)b->channel_count - first;
    if (used > LG_OUTPUT_CHANNELS_PER_CHIP)
        used = LG_OUTPUT_CHANNELS_PER_CHIP;
    for (size_t i = used; i < LG_OUTPUT_CHANNELS_PER_CHIP; ++i) {
        if (pwm[i] != 0u) return false;
    }
    memcpy(&b->staged_pwm[first], pwm, used);
    ++b->write_blocks_received;
    if (b->write_blocks_received != b->write_blocks_expected) return true;

    uint8_t chip_pwm[IS31FL3236A_MAX_GROUP_CHIPS][IS31FL3236A_CHANNELS] = {{0}};
    uint8_t chip_control[IS31FL3236A_MAX_GROUP_CHIPS][IS31FL3236A_CHANNELS] = {{0}};
    for (size_t channel = 0u; channel < b->channel_count; ++channel) {
        size_t chip = channel / IS31FL3236A_CHANNELS;
        size_t output = channel % IS31FL3236A_CHANNELS;
        chip_pwm[chip][output] = b->staged_pwm[channel];
        chip_control[chip][output] = b->current_profile[channel];
    }
    b->frame_staged = is31fl3236a_stage_group_frame(
        b->drivers, b->driver_count,
        (const uint8_t (*)[IS31FL3236A_CHANNELS])chip_pwm,
        (const uint8_t (*)[IS31FL3236A_CHANNELS])chip_control);
    return b->frame_staged;
}

static lg_output_io make_output_io(is31fl3236a_lamp_backend *b) {
    lg_output_io io = {0};
    io.context = b;
    io.set_oe_n = backend_set_oe_n;
    io.set_power_request = backend_set_power_request;
    io.write_chip_pwm = backend_write_chip_pwm;
    io.power_good = backend_power_good;
    return io;
}

static bool initialize_driver_group(is31fl3236a_lamp_backend *b) {
    for (uint8_t i = 0u; i < b->driver_count; ++i) {
        if (!is31fl3236a_initialize(&b->drivers[i], &b->driver_io,
                                    b->addresses_7bit[i], b->pwm_frequency))
            return false;
    }
    b->driver_group_initialized = true;
    return true;
}

bool is31fl3236a_lamp_backend_init(
    is31fl3236a_lamp_backend *b,
    const is31fl3236a_io *driver_io,
    const uint8_t *addresses, size_t driver_count,
    uint16_t channel_count,
    const uint8_t *channel_control_profile, size_t profile_length,
    is31fl3236a_pwm_frequency pwm_frequency,
    is31fl3236a_led_power_request_fn set_led_power_request,
    is31fl3236a_led_power_good_fn led_power_good,
    void *power_context,
    uint32_t power_ready_timeout_ms) {
    if (!b) return false;
    memset(b, 0, sizeof(*b));
    if (!backend_io_valid(driver_io) || !addresses || !channel_control_profile ||
        !set_led_power_request || !led_power_good ||
        (channel_count != 61u && channel_count != 122u) ||
        profile_length != channel_count ||
        driver_count != (size_t)((channel_count + IS31FL3236A_CHANNELS - 1u) /
                                 IS31FL3236A_CHANNELS) ||
        driver_count > IS31FL3236A_MAX_GROUP_CHIPS ||
        (pwm_frequency != IS31FL3236A_PWM_3KHZ &&
         pwm_frequency != IS31FL3236A_PWM_22KHZ))
        return false;
    for (size_t i = 0u; i < driver_count; ++i) {
        if (addresses[i] < 0x08u || addresses[i] > 0x77u) return false;
        for (size_t j = 0u; j < i; ++j) {
            if (addresses[i] == addresses[j]) return false;
        }
    }
    for (size_t i = 0u; i < profile_length; ++i) {
        if ((channel_control_profile[i] & (uint8_t)~LAMP_PROFILE_MASK) != 0u)
            return false;
    }

    b->driver_io = *driver_io;
    b->set_led_power_request = set_led_power_request;
    b->led_power_good = led_power_good;
    b->power_context = power_context;
    b->channel_count = channel_count;
    b->driver_count = (uint8_t)driver_count;
    b->write_blocks_expected = (uint8_t)((channel_count + 23u) / 24u);
    b->pwm_frequency = pwm_frequency;
    memcpy(b->addresses_7bit, addresses, driver_count);
    memcpy(b->current_profile, channel_control_profile, profile_length);

    if (!set_led_power_request(power_context, false) ||
        !driver_io->set_sdb_low(driver_io->context, true) ||
        !driver_io->sdb_is_low(driver_io->context) ||
        !driver_io->driver_supply_is_valid(driver_io->context)) {
        (void)driver_io->force_outputs_off(driver_io->context, true);
        (void)set_led_power_request(power_context, false);
        return false;
    }
    if (!initialize_driver_group(b)) {
        b->driver_group_initialized = false;
        (void)driver_io->set_sdb_low(driver_io->context, true);
        (void)driver_io->force_outputs_off(driver_io->context, true);
        (void)set_led_power_request(power_context, false);
        return false;
    }

    lg_output_io output_io = make_output_io(b);
    if (!lg_output_init(&b->output, &output_io, power_ready_timeout_ms)) {
        b->driver_group_initialized = false;
        (void)driver_io->set_sdb_low(driver_io->context, true);
        (void)driver_io->force_outputs_off(driver_io->context, true);
        (void)set_led_power_request(power_context, false);
        return false;
    }
    b->initialized = true;
    return true;
}

lg_output_status is31fl3236a_lamp_backend_sync(
    is31fl3236a_lamp_backend *b, const lg_guard *guard,
    uint32_t now_ms) {
    if (!b || !b->initialized) return LG_OUTPUT_LATCHED_FAULT;
    return lg_output_sync(&b->output, guard, now_ms);
}

bool is31fl3236a_lamp_backend_recover(
    is31fl3236a_lamp_backend *b, const lg_guard *guard) {
    if (!b || !b->initialized || !guard) return false;
    bool was_initialized = b->driver_group_initialized;
    b->driver_group_initialized = false;
    bool sdb_command_ok =
        b->driver_io.set_sdb_low(b->driver_io.context, true);
    bool sdb_readback_ok =
        b->driver_io.sdb_is_low(b->driver_io.context);
    bool hardware_off_ok =
        b->driver_io.force_outputs_off(b->driver_io.context, true);
    bool led_rail_off_ok =
        b->set_led_power_request(b->power_context, false);
    b->led_power_requested = false;
    if (!sdb_command_ok || !sdb_readback_ok || !hardware_off_ok ||
        !led_rail_off_ok || !lg_output_recover(&b->output, guard)) {
        b->driver_group_initialized = was_initialized;
        return false;
    }

    memset(b->drivers, 0, sizeof(b->drivers));
    if (!initialize_driver_group(b)) {
        b->driver_group_initialized = false;
        b->output.fault_latched = true;
        b->output.fault_session_id = guard->session_id;
        (void)b->driver_io.set_sdb_low(b->driver_io.context, true);
        (void)b->driver_io.force_outputs_off(b->driver_io.context, true);
        (void)b->set_led_power_request(b->power_context, false);
        b->led_power_requested = false;
        return false;
    }
    return true;
}
