#include "lamp_output_adapter.h"

#include <limits.h>
#include <stddef.h>
#include <string.h>

static bool callbacks_valid(const lg_output_io *io) {
    return io && io->set_oe_n && io->set_power_request &&
           io->write_chip_pwm && io->power_good;
}

static bool command_oe_n(lg_output *o, bool high) {
    if (!o->io.set_oe_n(o->io.context, high)) return false;
    o->oe_n_high = high;
    return true;
}

static bool command_power(lg_output *o, bool enabled) {
    if (!o->io.set_power_request(o->io.context, enabled)) return false;
    o->power_requested = enabled;
    return true;
}

static bool force_off(lg_output *o, bool latch) {
    bool oe_ok = o->io.set_oe_n(o->io.context, true);
    bool power_ok = o->io.set_power_request(o->io.context, false);
    if (oe_ok) o->oe_n_high = true;
    if (power_ok) o->power_requested = false;
    o->waiting_for_power = false;
    o->pwm_written = false;
    o->written_channels = 0u;
    memset(o->written_pwm, 0, sizeof(o->written_pwm));
    if (latch || !oe_ok || !power_ok) o->fault_latched = true;
    return oe_ok && power_ok;
}

static void latch_fault(lg_output *o, const lg_guard *g) {
    o->fault_session_id = (g && g->configured) ? g->session_id : 0u;
    (void)force_off(o, true);
}

static bool guard_requests_output(const lg_guard *g) {
    return g && g->configured && g->armed && g->output_enable &&
           (g->mode == LG_AI || g->mode == LG_MANUAL) &&
           g->hand_present && g->inputs.temperature_ok &&
           g->inputs.supply_ok && g->inputs.led_driver_ok &&
           g->inputs.hardware_interlock_ok && g->cfg.channels > 0u &&
           g->cfg.channels <= LG_MAX_CHANNELS;
}

static bool same_pwm(const lg_output *o, const lg_guard *g) {
    return o->pwm_written && o->written_channels == g->cfg.channels &&
           memcmp(o->written_pwm, g->pwm, g->cfg.channels) == 0;
}

static bool write_all_chips(lg_output *o, const lg_guard *g) {
    uint16_t channels = g->cfg.channels;
    uint8_t chip_pwm[LG_OUTPUT_CHANNELS_PER_CHIP];
    uint8_t chips = (uint8_t)((channels + LG_OUTPUT_CHANNELS_PER_CHIP - 1u) /
                              LG_OUTPUT_CHANNELS_PER_CHIP);
    for (uint8_t chip = 0; chip < chips; ++chip) {
        uint16_t first = (uint16_t)chip * LG_OUTPUT_CHANNELS_PER_CHIP;
        uint16_t count = channels - first;
        if (count > LG_OUTPUT_CHANNELS_PER_CHIP)
            count = LG_OUTPUT_CHANNELS_PER_CHIP;
        memset(chip_pwm, 0, sizeof(chip_pwm));
        memcpy(chip_pwm, &g->pwm[first], count);
        if (!o->io.write_chip_pwm(o->io.context, chip, chip_pwm)) return false;
    }
    memset(o->written_pwm, 0, sizeof(o->written_pwm));
    memcpy(o->written_pwm, g->pwm, channels);
    o->written_channels = channels;
    o->pwm_written = true;
    return true;
}

bool lg_output_init(lg_output *o, const lg_output_io *io,
                    uint32_t power_ready_timeout_ms) {
    if (!o) return false;
    memset(o, 0, sizeof(*o));
    if (!callbacks_valid(io) || power_ready_timeout_ms == 0u ||
        power_ready_timeout_ms >= (uint32_t)INT32_MAX)
        return false;
    o->io = *io;
    o->power_ready_timeout_ms = power_ready_timeout_ms;
    o->initialized = true;
    return force_off(o, false);
}

lg_output_status lg_output_sync(lg_output *o, const lg_guard *g,
                                uint32_t now_ms) {
    if (!o || !o->initialized) return LG_OUTPUT_LATCHED_FAULT;
    if (o->fault_latched) {
        (void)force_off(o, true);
        return LG_OUTPUT_LATCHED_FAULT;
    }
    if (g && g->configured && g->session_id != 0u) {
        if (!o->session_bound) {
            o->owner_session_id = g->session_id;
            o->session_bound = true;
        } else if (o->owner_session_id != g->session_id) {
            o->fault_session_id = o->owner_session_id;
            (void)force_off(o, true);
            return LG_OUTPUT_LATCHED_FAULT;
        }
    }
    if (!guard_requests_output(g)) {
        if (!force_off(o, false)) {
            o->fault_session_id = (g && g->configured) ? g->session_id : 0u;
            return LG_OUTPUT_LATCHED_FAULT;
        }
        return LG_OUTPUT_OFF;
    }

    bool changed = !same_pwm(o, g);
    if (changed) {
        if (!command_oe_n(o, true)) {
            latch_fault(o, g);
            return LG_OUTPUT_LATCHED_FAULT;
        }
    }
    if (changed && !write_all_chips(o, g)) {
        latch_fault(o, g);
        return LG_OUTPUT_LATCHED_FAULT;
    }

    if (!o->power_requested) {
        if (!command_power(o, true)) {
            latch_fault(o, g);
            return LG_OUTPUT_LATCHED_FAULT;
        }
        o->waiting_for_power = true;
        o->power_wait_started_ms = now_ms;
    }

    if (o->io.power_good(o->io.context)) {
        if (!o->oe_n_high) return LG_OUTPUT_ON;
        if (!command_oe_n(o, false)) {
            latch_fault(o, g);
            return LG_OUTPUT_LATCHED_FAULT;
        }
        o->waiting_for_power = false;
        return LG_OUTPUT_ON;
    }

    /* A previously active rail that drops power-good is blanked immediately. */
    if (!o->oe_n_high) {
        if (!command_oe_n(o, true)) {
            latch_fault(o, g);
            return LG_OUTPUT_LATCHED_FAULT;
        }
    }
    if (!o->waiting_for_power) {
        o->waiting_for_power = true;
        o->power_wait_started_ms = now_ms;
    }
    if ((uint32_t)(now_ms - o->power_wait_started_ms) >=
        o->power_ready_timeout_ms) {
        latch_fault(o, g);
        return LG_OUTPUT_LATCHED_FAULT;
    }
    return LG_OUTPUT_PENDING;
}

bool lg_output_recover(lg_output *o, const lg_guard *g) {
    if (!o || !o->initialized || !g || g->session_id == 0u || g->armed ||
        g->mode != LG_OFF || g->session_id == o->fault_session_id)
        return false;
    if (!force_off(o, false)) return false;
    o->fault_latched = false;
    o->fault_session_id = 0u;
    o->owner_session_id = g->session_id;
    o->session_bound = true;
    return true;
}
