/*
 * test_rgb_fsm.c — RGB Status LED State Machine tests (Issue #290)
 *
 * Verifies:
 *   - State transitions and initial state
 *   - Animation progression for each LED state
 *   - Timing and animation cycles
 *   - State-specific animations (pulse, breathe, spin, blink, fill)
 *   - Edge cases and invalid inputs
 */

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* ── LED State Machine Simulator ──────────────────────────────────*/

typedef enum {
    LED_STATE_BOOT = 0,
    LED_STATE_IDLE,
    LED_STATE_ARMED,
    LED_STATE_NAV,
    LED_STATE_ERROR,
    LED_STATE_LOW_BATT,
    LED_STATE_CHARGING,
    LED_STATE_ESTOP,
    LED_STATE_COUNT
} LedState;

typedef struct {
    LedState current_state;
    LedState previous_state;
    uint32_t state_start_time_ms;
    uint32_t last_tick_ms;
    uint8_t animation_frame;
} RgbFsm;

static RgbFsm sim = {0};

void sim_init(void) {
    memset(&sim, 0, sizeof(sim));
    sim.current_state = LED_STATE_BOOT;
    sim.previous_state = LED_STATE_BOOT;
}

bool sim_set_state(LedState state) {
    if (state >= LED_STATE_COUNT) return false;
    if (state == sim.current_state) return false;
    sim.previous_state = sim.current_state;
    sim.current_state = state;
    sim.state_start_time_ms = (uint32_t)-1;
    sim.animation_frame = 0;
    return true;
}

LedState sim_get_state(void) {
    return sim.current_state;
}

void sim_tick(uint32_t now_ms) {
    if (sim.state_start_time_ms == (uint32_t)-1) {
        sim.state_start_time_ms = now_ms;
        return;
    }

    uint32_t elapsed = now_ms - sim.state_start_time_ms;

    switch (sim.current_state) {
        case LED_STATE_BOOT:
            sim.animation_frame = (elapsed % 2000) * 255 / 2000;
            break;
        case LED_STATE_IDLE:
            sim.animation_frame = (elapsed % 2000) * 255 / 2000;
            break;
        case LED_STATE_ARMED:
            sim.animation_frame = 255;
            break;
        case LED_STATE_NAV:
            sim.animation_frame = ((elapsed % 1000) / 125) * 32;
            break;
        case LED_STATE_ERROR:
            sim.animation_frame = ((elapsed % 500) < 250) ? 255 : 0;
            break;
        case LED_STATE_LOW_BATT:
            sim.animation_frame = ((elapsed % 1000) < 500) ? 255 : 0;
            break;
        case LED_STATE_CHARGING:
            sim.animation_frame = ((elapsed % 1000) / 125) * 32;
            break;
        case LED_STATE_ESTOP:
            sim.animation_frame = 255;
            break;
        default:
            break;
    }

    sim.last_tick_ms = now_ms;
}

/* ── Unit Tests ────────────────────────────────────────────────────────*/

static int test_count = 0, test_passed = 0, test_failed = 0;

#define TEST(name) do { test_count++; printf("\n  TEST %d: %s\n", test_count, name); } while(0)
#define ASSERT(cond, msg) do { if (cond) { test_passed++; printf("    ✓ %s\n", msg); } else { test_failed++; printf("    ✗ %s\n", msg); } } while(0)

void test_initial_state(void) {
    TEST("Initial state is BOOT");
    sim_init();
    ASSERT(sim_get_state() == LED_STATE_BOOT, "State is BOOT");
}

void test_state_transitions(void) {
    TEST("State transitions work correctly");
    sim_init();
    bool result = sim_set_state(LED_STATE_IDLE);
    ASSERT(result == true, "Transition succeeds");
    ASSERT(sim_get_state() == LED_STATE_IDLE, "State changed");
    result = sim_set_state(LED_STATE_IDLE);
    ASSERT(result == false, "Same state returns false");
}

void test_all_states(void) {
    TEST("All 8 states are accessible");
    sim_init();
    for (int i = 1; i < LED_STATE_COUNT; i++) {
        bool result = sim_set_state((LedState)i);
        ASSERT(result == true, "State transition succeeds");
    }
}

void test_boot_animation(void) {
    TEST("BOOT state animates");
    sim_init();
    sim_set_state(LED_STATE_BOOT);
    sim_tick(0);
    sim_tick(500);
    uint8_t frame = sim.animation_frame;
    ASSERT(frame > 0 && frame < 255, "Animation progresses");
}

void test_idle_animation(void) {
    TEST("IDLE state animates");
    sim_init();
    sim_set_state(LED_STATE_IDLE);
    sim_tick(0);
    sim_tick(500);
    ASSERT(sim.animation_frame > 0, "Animation starts");
}

void test_armed_static(void) {
    TEST("ARMED state is static");
    sim_init();
    sim_set_state(LED_STATE_ARMED);
    sim_tick(0);
    sim_tick(100);
    ASSERT(sim.animation_frame == 255, "No animation");
}

void test_nav_animation(void) {
    TEST("NAV state spins");
    sim_init();
    sim_set_state(LED_STATE_NAV);
    sim_tick(0);
    sim_tick(150);
    ASSERT(sim.animation_frame > 0, "Animation starts");
}

void test_error_animation(void) {
    TEST("ERROR state flashes");
    sim_init();
    sim_set_state(LED_STATE_ERROR);
    sim_tick(0);
    sim_tick(100);
    ASSERT(sim.animation_frame == 255, "Bright state");
    sim_tick(300);
    ASSERT(sim.animation_frame == 0, "Dark state");
}

void test_low_batt_animation(void) {
    TEST("LOW_BATT state blinks");
    sim_init();
    sim_set_state(LED_STATE_LOW_BATT);
    sim_tick(0);
    sim_tick(100);
    ASSERT(sim.animation_frame == 255, "Bright");
    sim_tick(600);
    ASSERT(sim.animation_frame == 0, "Dark");
}

void test_charging_animation(void) {
    TEST("CHARGING state fills");
    sim_init();
    sim_set_state(LED_STATE_CHARGING);
    sim_tick(0);
    sim_tick(200);
    ASSERT(sim.animation_frame > 0, "Animation progresses");
}

void test_estop_static(void) {
    TEST("ESTOP state is static");
    sim_init();
    sim_set_state(LED_STATE_ESTOP);
    sim_tick(0);
    sim_tick(100);
    ASSERT(sim.animation_frame == 255, "Solid red");
}

void test_state_reset(void) {
    TEST("State change resets timing");
    sim_init();
    sim_set_state(LED_STATE_BOOT);
    sim_tick(0);
    sim_tick(500);
    uint8_t boot_frame = sim.animation_frame;
    sim_set_state(LED_STATE_IDLE);
    sim_tick(0);
    sim_tick(100);
    uint8_t idle_frame = sim.animation_frame;
    ASSERT(idle_frame < boot_frame, "Fresh animation start");
}

void test_invalid_state(void) {
    TEST("Invalid state rejected");
    sim_init();
    bool result = sim_set_state((LedState)255);
    ASSERT(result == false, "Invalid rejected");
}

void test_rapid_changes(void) {
    TEST("Rapid transitions work");
    sim_init();
    sim_set_state(LED_STATE_IDLE);
    ASSERT(sim_get_state() == LED_STATE_IDLE, "In IDLE");
    sim_set_state(LED_STATE_ERROR);
    ASSERT(sim_get_state() == LED_STATE_ERROR, "In ERROR");
}

int main(void) {
    printf("\n══════════════════════════════════════════════════════════════\n");
    printf("  RGB Status LED State Machine — Unit Tests (Issue #290)\n");
    printf("══════════════════════════════════════════════════════════════\n");

    test_initial_state();
    test_state_transitions();
    test_all_states();
    test_boot_animation();
    test_idle_animation();
    test_armed_static();
    test_nav_animation();
    test_error_animation();
    test_low_batt_animation();
    test_charging_animation();
    test_estop_static();
    test_state_reset();
    test_invalid_state();
    test_rapid_changes();

    printf("\n──────────────────────────────────────────────────────────────\n");
    printf("  Results: %d/%d tests passed, %d failed\n", test_passed, test_count, test_failed);
    printf("──────────────────────────────────────────────────────────────\n\n");

    return (test_failed == 0) ? 0 : 1;
}
