// Host regression for the raw single/double/long click state machine.
// Display drawing, task scheduling and USB are still checked on device.
#include "button_gesture.h"
#include <cassert>
#include <cstdio>
#include <vector>

namespace {

constexpr uint64_t DEBOUNCE = ButtonGesture::DEBOUNCE_MS;   // 30
constexpr uint64_t LONG = ButtonGesture::LONG_MS;           // 600
constexpr uint64_t GAP = ButtonGesture::DOUBLE_GAP_MS;      // 300

// Feed `raw` held from `from` to `to` at a 1 ms cadence and collect gestures.
void feed(ButtonGesture &button, bool raw, uint64_t from, uint64_t to,
          std::vector<Gesture> &out, uint64_t epoch = 1, bool allow_long = true,
          bool allow_double = true) {
    for (uint64_t now = from; now <= to; ++now) {
        const auto g = button.update(raw, now, epoch, allow_long, allow_double);
        if (g != Gesture::NONE) out.push_back(g);
    }
}

size_t count(const std::vector<Gesture> &v, Gesture g) {
    size_t n = 0;
    for (auto x : v) if (x == g) ++n;
    return n;
}

// One press geometry: raw down at `down`, raw up at `up`. Returns the tick at
// which the release is debounce-confirmed, so the caller can continue from there.
uint64_t press(ButtonGesture &button, uint64_t down, uint64_t up,
               std::vector<Gesture> &out, uint64_t epoch = 1, bool allow_long = true,
               bool allow_double = true) {
    if (down > 0) feed(button, false, down - 1, down - 1, out, epoch, allow_long, allow_double);
    feed(button, true, down, up, out, epoch, allow_long, allow_double);
    const uint64_t confirmed = up + DEBOUNCE + 1;
    feed(button, false, up + 1, confirmed, out, epoch, allow_long, allow_double);
    return confirmed;
}

// Short press. Returns the tick at which the release is debounce-confirmed,
// which is also when a pending CLICK is armed and its gap begins.
uint64_t short_press(ButtonGesture &button, uint64_t down, std::vector<Gesture> &out,
                     uint64_t epoch = 1, bool allow_double = true) {
    return press(button, down, down + 60, out, epoch, true, allow_double);
}

// Run the idle line up to `until` so pending deadlines can fire.
void idle_to(ButtonGesture &button, uint64_t from, uint64_t until,
             std::vector<Gesture> &out, uint64_t epoch = 1, bool allow_double = true) {
    feed(button, false, from, until, out, epoch, true, allow_double);
}

// A held press must end with exactly one LONG_RELEASE and never a trailing CLICK.
void long_release_check(ButtonGesture &button, uint64_t confirmed,
                        std::vector<Gesture> &out) {
    idle_to(button, confirmed, confirmed + 2000, out);
    assert(count(out, Gesture::LONG_RELEASE) == 1);
    assert(out.back() == Gesture::LONG_RELEASE);
    assert(count(out, Gesture::CLICK) == 0);
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
}

void test_single() {
    ButtonGesture button;
    std::vector<Gesture> out;
    const uint64_t armed = short_press(button, 1000, out);   // 1090
    assert(out.empty());                                     // No CLICK before the gap.
    assert(button.pending_click());
    idle_to(button, armed, armed + GAP - 2, out);
    assert(out.empty());                                     // Gap not yet elapsed.
    idle_to(button, armed + GAP - 1, armed + GAP + 5, out);
    assert(out.size() == 1 && out[0] == Gesture::CLICK);
    assert(!button.pending_click());
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    std::puts("single press waits the full gap then emits exactly one CLICK PASS");
}

void test_double() {
    ButtonGesture button;
    std::vector<Gesture> out;
    const uint64_t armed = short_press(button, 1000, out);   // 1090
    idle_to(button, armed, armed + 40, out);
    assert(out.empty());
    short_press(button, armed + 41, out);                    // second tap inside gap
    assert(out.size() == 1 && out[0] == Gesture::DOUBLE_CLICK);
    assert(count(out, Gesture::CLICK) == 0);                 // ZERO leading CLICK.
    idle_to(button, armed + 200, armed + 2000, out);
    assert(out.size() == 1);
    assert(count(out, Gesture::CLICK) == 0);
    std::puts("double press emits DOUBLE_CLICK with zero leading CLICK PASS");
}

void test_gap_boundary() {
    // 299 ms raw-down gap after confirmation: the second press owns the pending
    // click before the deadline, so it resolves as a DOUBLE.
    {
        ButtonGesture button;
        std::vector<Gesture> out;
        const uint64_t armed = short_press(button, 1000, out);
        idle_to(button, armed, armed + 299 - 1, out);
        assert(out.empty());
        press(button, armed + 299, armed + 299 + 60, out);
        assert(count(out, Gesture::DOUBLE_CLICK) == 1);
        assert(count(out, Gesture::CLICK) == 0);
    }
    // 301 ms: the pending CLICK expires on the idle line, then the late press is
    // an independent sequence -> two SINGLEs.
    {
        ButtonGesture button;
        std::vector<Gesture> out;
        const uint64_t armed = short_press(button, 1000, out);
        idle_to(button, armed, armed + 301, out);
        assert(count(out, Gesture::CLICK) == 1);
        short_press(button, armed + 310, out);
        idle_to(button, armed + 400, armed + 2000, out);
        assert(count(out, Gesture::DOUBLE_CLICK) == 0);
        assert(count(out, Gesture::CLICK) == 2);
    }
    // Exactly DOUBLE_GAP_MS while idle: the deadline is inclusive and expires.
    {
        ButtonGesture button;
        std::vector<Gesture> out;
        const uint64_t armed = short_press(button, 1000, out);
        idle_to(button, armed, armed + GAP, out);
        assert(count(out, Gesture::CLICK) == 1);
        assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    }
    std::puts("299 -> DOUBLE; 301 / exact gap -> first SINGLE then next sequence PASS");
}

void test_long() {
    ButtonGesture button;
    std::vector<Gesture> out;
    const uint64_t confirmed =
        press(button, 1000, 1000 + LONG + DEBOUNCE + 10, out);
    assert(count(out, Gesture::LONG) == 1);
    long_release_check(button, confirmed, out);
    std::puts("long press emits one LONG and one LONG_RELEASE with zero CLICK PASS");
}

void test_short_then_long_wins() {
    ButtonGesture button;
    std::vector<Gesture> out;
    const uint64_t armed = short_press(button, 1000, out);
    idle_to(button, armed, armed + 9, out);
    assert(out.empty());
    // Second press begins inside the gap and is held past LONG_MS.
    const uint64_t down = armed + 10;
    const uint64_t confirmed = press(button, down, down + LONG + DEBOUNCE + 10, out);
    assert(count(out, Gesture::LONG) == 1);
    assert(count(out, Gesture::CLICK) == 0);
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    long_release_check(button, confirmed, out);
    std::puts("first short + second long -> LONG only, then LONG_RELEASE PASS");
}

void test_cross_button() {
    // A then B is not a double: each object only ever sees its own line.
    ButtonGesture a, b;
    std::vector<Gesture> out;
    const uint64_t armed = short_press(a, 1000, out);
    idle_to(a, armed, armed + 9, out);
    const uint64_t armed_b = short_press(b, armed + 10, out);
    idle_to(a, armed + 100, armed_b + GAP + 20, out);
    idle_to(b, armed + 100, armed_b + GAP + 20, out);
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    assert(count(out, Gesture::CLICK) == 2);
    // B then A likewise.
    ButtonGesture a2, b2;
    std::vector<Gesture> out2;
    const uint64_t armed2 = short_press(b2, 3000, out2);
    idle_to(b2, armed2, armed2 + 9, out2);
    const uint64_t armed2_a = short_press(a2, armed2 + 10, out2);
    idle_to(a2, armed2 + 100, armed2_a + GAP + 20, out2);
    idle_to(b2, armed2 + 100, armed2_a + GAP + 20, out2);
    assert(count(out2, Gesture::DOUBLE_CLICK) == 0);
    assert(count(out2, Gesture::CLICK) == 2);
    std::puts("A->B / B->A never produce a DOUBLE_CLICK PASS");
}

void test_bounce() {
    ButtonGesture button;
    std::vector<Gesture> out;
    // Contact bounce: each raw blip is shorter than DEBOUNCE and returns to the
    // idle level before the debounce can confirm a press.
    feed(button, false, 0, 999, out);
    feed(button, true, 1000, 1010, out);
    feed(button, false, 1011, 1015, out);
    feed(button, true, 1016, 1020, out);
    feed(button, false, 1021, 1500, out);
    assert(out.empty());
    assert(!button.down() && !button.pending_click());
    // A confirmed short press still disambiguates normally afterwards.
    const uint64_t armed = short_press(button, 2000, out);
    idle_to(button, armed, armed + GAP + 5, out);
    assert(count(out, Gesture::CLICK) == 1);
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    std::puts("contact bounce produces no spurious gesture PASS");
}

void test_stale_epoch() {
    // UI epoch changes while a single click is pending: it must be discarded.
    ButtonGesture button;
    std::vector<Gesture> out;
    const uint64_t armed = short_press(button, 1000, out, 1);
    idle_to(button, armed, armed + 9, out, 1);
    idle_to(button, armed + 10, armed + GAP + 5, out, 2);   // epoch moved
    assert(count(out, Gesture::CLICK) == 0);
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    // A fresh single in the new epoch still behaves normally.
    const uint64_t armed2 = short_press(button, 2000, out, 2);
    idle_to(button, armed2, armed2 + GAP + 5, out, 2);
    assert(count(out, Gesture::CLICK) == 1);
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    // Explicit invalidation (modal/approval/overlay ownership change).
    ButtonGesture invalidated;
    std::vector<Gesture> out2;
    const uint64_t armed3 = short_press(invalidated, 1000, out2);
    assert(invalidated.pending_click());
    invalidated.invalidate_pending();
    assert(!invalidated.pending_click());
    idle_to(invalidated, armed3, armed3 + GAP + 5, out2);
    assert(count(out2, Gesture::CLICK) == 0);
    // A pending click whose epoch moved cannot become a DOUBLE either.
    ButtonGesture moved;
    std::vector<Gesture> out3;
    const uint64_t armed4 = short_press(moved, 1000, out3, 1);
    idle_to(moved, armed4, armed4 + 9, out3, 1);
    short_press(moved, armed4 + 10, out3, 2);
    idle_to(moved, armed4 + 200, armed4 + GAP + 20, out3, 2);
    assert(count(out3, Gesture::DOUBLE_CLICK) == 0);
    std::puts("stale epoch / explicit invalidation discard pending CLICK PASS");
}

void test_double_disabled() {
    // allow_double false (approval/modal/overlay): immediate CLICK, no delay.
    ButtonGesture button;
    std::vector<Gesture> out;
    press(button, 1000, 1060, out, 1, true, false);
    assert(count(out, Gesture::CLICK) == 1);   // No 300 ms wait.
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    assert(!button.pending_click());
    // Two fast presses with allow_double false stay two immediate CLICKs.
    press(button, 1080, 1140, out, 1, true, false);
    assert(count(out, Gesture::CLICK) == 2);
    assert(count(out, Gesture::DOUBLE_CLICK) == 0);
    // allow_long false: a >600 ms hold still yields exactly one CLICK.
    ButtonGesture no_long;
    std::vector<Gesture> out3;
    press(no_long, 3000, 3000 + LONG + DEBOUNCE + 100, out3, 1, false, false);
    assert(count(out3, Gesture::LONG) == 0);
    assert(count(out3, Gesture::LONG_RELEASE) == 0);
    assert(count(out3, Gesture::CLICK) == 1);
    std::puts("allow_double=false keeps immediate CLICK and never doubles PASS");
}

} // namespace

int main() {
    test_single();
    test_double();
    test_gap_boundary();
    test_long();
    test_short_then_long_wins();
    test_cross_button();
    test_bounce();
    test_stale_epoch();
    test_double_disabled();
    std::puts("Raw ButtonGesture single/double/long state machine PASS (host)");
}