#include "lamp_program.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>

static unsigned checks;
static const lg_inputs good = {true, true, true, true};

static lg_guard fresh_guard(void) {
    lg_guard guard;
    lg_config config = {0};
    config.channels = 61;
    config.max_run_ms = 150000u;
    config.ticket_lifetime_ms = 200u;
    config.max_poll_gap_ms = 20u;
    config.calibration_id = 17u;
    config.fixed_input_mw = 4000u;
    config.input_budget_mw = 20400u;
    for (unsigned i = 0; i < LG_MAX_CHANNELS; ++i)
        config.channel_input_mw[i] = 300u;
    assert(lg_init(&guard, &config, UINT64_C(0x1020304050607080), 0u));
    lg_poll(&guard, 0u, good);
    return guard;
}

static void expect(lg_mode mode, lp_recipe recipe, bool accepted, uint32_t expected) {
    uint32_t duration = UINT32_C(0xA5A5A5A5);
    bool result = lp_resolve_duration(mode, recipe, &duration);
    assert(result == accepted);
    if (accepted) assert(duration == expected);
    else assert(duration == UINT32_C(0xA5A5A5A5));
    ++checks;
}

static void check_arm(lg_mode mode, lp_recipe recipe, bool accepted,
                      uint32_t expected) {
    lg_guard guard = fresh_guard();
    bool result = lp_arm_recipe(&guard, mode, recipe, 0u);
    assert(result == accepted);
    if (accepted) {
        assert(guard.armed && guard.mode == mode);
        assert(guard.run_duration_ms == expected);
    } else {
        assert(!guard.armed && !guard.output_enable);
    }
    ++checks;
}

static void check_expiry(lg_mode mode, lp_recipe recipe, uint32_t duration_ms) {
    lg_guard guard = fresh_guard();
    assert(lp_arm_recipe(&guard, mode, recipe, 0u));
    for (uint32_t now = 20u; now < duration_ms; now += 20u)
        lg_poll(&guard, now, good);
    lg_poll(&guard, duration_ms, good);
    assert(!guard.armed && !guard.output_enable && guard.mode == LG_OFF);
    ++checks;
}

static void expect_one_touch(lg_mode current_mode, lp_recipe recipe,
                             bool accepted, lg_mode expected_mode,
                             uint32_t expected_duration) {
    lg_mode resolved_mode = LG_FAULT;
    uint32_t duration = UINT32_C(0xA5A5A5A5);
    bool result = lp_resolve_one_touch_mode(current_mode, recipe,
                                            &resolved_mode, &duration);
    assert(result == accepted);
    if (accepted) {
        assert(resolved_mode == expected_mode);
        assert(duration == expected_duration);
    } else {
        assert(resolved_mode == LG_FAULT);
        assert(duration == UINT32_C(0xA5A5A5A5));
    }
    ++checks;
}

static void check_one_touch_arm(lg_mode current_mode, lp_recipe recipe,
                                lg_mode expected_mode,
                                uint32_t expected_duration) {
    lg_guard guard = fresh_guard();
    assert(lp_arm_one_touch_recipe(&guard, current_mode, recipe, 0u));
    assert(guard.armed && guard.mode == expected_mode);
    assert(guard.run_duration_ms == expected_duration);
    ++checks;
}

int main(void) {
    expect(LG_AI, LP_BASE_COLOR, true, 45000u);
    expect(LG_AI, LP_BUILDER_EXTENSION, true, 90000u);
    expect(LG_AI, LP_DARK_GEL, false, 0u);
    expect(LG_AI, LP_TOP_COAT, false, 0u);
    expect(LG_AI, LP_MANUAL_60, false, 0u);
    expect(LG_AI, LP_MANUAL_120, false, 0u);
    expect(LG_MANUAL, LP_BASE_COLOR, true, 45000u);
    expect(LG_MANUAL, LP_DARK_GEL, true, 120000u);
    expect(LG_MANUAL, LP_BUILDER_EXTENSION, true, 90000u);
    expect(LG_MANUAL, LP_TOP_COAT, true, 120000u);
    expect(LG_MANUAL, LP_MANUAL_60, true, 60000u);
    expect(LG_MANUAL, LP_MANUAL_120, true, 120000u);
    expect(LG_OFF, LP_BASE_COLOR, false, 0u);
    expect(LG_MANUAL, LP_RECIPE_COUNT, false, 0u);
    expect(LG_MANUAL, (lp_recipe)-1, false, 0u);
    assert(!lp_resolve_duration(LG_MANUAL, LP_BASE_COLOR, NULL));
    ++checks;
    expect_one_touch(LG_OFF, LP_BASE_COLOR, true, LG_AI, 45000u);
    expect_one_touch(LG_AI, LP_BASE_COLOR, true, LG_AI, 45000u);
    expect_one_touch(LG_AI, LP_BUILDER_EXTENSION, true, LG_AI, 90000u);
    expect_one_touch(LG_AI, LP_DARK_GEL, true, LG_MANUAL, 120000u);
    expect_one_touch(LG_AI, LP_TOP_COAT, true, LG_MANUAL, 120000u);
    expect_one_touch(LG_MANUAL, LP_DARK_GEL, true, LG_MANUAL, 120000u);
    expect_one_touch(LG_OFF, LP_MANUAL_60, false, LG_FAULT, 0u);
    expect_one_touch(LG_FAULT, LP_DARK_GEL, false, LG_FAULT, 0u);
    expect_one_touch(LG_AI, LP_RECIPE_COUNT, false, LG_FAULT, 0u);
    {
        uint32_t duration = UINT32_C(0xA5A5A5A5);
        assert(!lp_resolve_one_touch_mode(LG_AI, LP_DARK_GEL, NULL,
                                          &duration));
        assert(duration == UINT32_C(0xA5A5A5A5));
        ++checks;
    }
    check_arm(LG_AI, LP_BASE_COLOR, true, 45000u);
    check_arm(LG_AI, LP_DARK_GEL, false, 0u);
    check_arm(LG_AI, LP_BUILDER_EXTENSION, true, 90000u);
    check_arm(LG_AI, LP_TOP_COAT, false, 0u);
    check_arm(LG_AI, LP_MANUAL_60, false, 0u);
    check_arm(LG_AI, LP_MANUAL_120, false, 0u);
    check_arm(LG_MANUAL, LP_BASE_COLOR, true, 45000u);
    check_arm(LG_MANUAL, LP_DARK_GEL, true, 120000u);
    check_arm(LG_MANUAL, LP_BUILDER_EXTENSION, true, 90000u);
    check_arm(LG_MANUAL, LP_TOP_COAT, true, 120000u);
    check_arm(LG_MANUAL, LP_MANUAL_60, true, 60000u);
    check_arm(LG_MANUAL, LP_MANUAL_120, true, 120000u);
    check_expiry(LG_AI, LP_BUILDER_EXTENSION, 90000u);
    check_expiry(LG_MANUAL, LP_MANUAL_120, 120000u);
    check_one_touch_arm(LG_AI, LP_DARK_GEL, LG_MANUAL, 120000u);
    check_one_touch_arm(LG_AI, LP_TOP_COAT, LG_MANUAL, 120000u);
    check_one_touch_arm(LG_OFF, LP_BUILDER_EXTENSION, LG_AI, 90000u);
    check_expiry(LG_MANUAL, LP_DARK_GEL, 120000u);
    printf("PASS: lamp program timer policy (%u checks)\n", checks);
    return 0;
}
