// Standalone tests for reasampler::mode_switch — no REAPER, no test framework.
// Same fast loop as the sibling pure tests (bank_grid et al.): assert the segmented
// mode-switch layout math and hit-testing directly.
//
// Covers (D5 brief §unit-test): N=2 and N=3 segment layout; exact tiling (adjacent
// segments abut with no gap/overlap, half-open boundaries — no pixel claimed twice,
// last segment reaches the header's right edge even when width doesn't divide
// evenly); hit-test hits per segment and misses (outside the band above/below/
// left/right, boundary pixels); degenerate widths and counts.

#include "../src/core/view/mode_switch.h"

#include <cstddef>
#include <cstdio>
#include <vector>

using namespace reasampler;
using namespace reasampler::view;

static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
    std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)

// --- Layout: N=2 -------------------------------------------------------------

// A header 100 wide at origin (10, 4), height 24, split in two: segments
// [10,60) and [60,110), each 50 wide, full header y/height.
static void testTwoSegmentsEvenSplit() {
    HeaderRect h{10, 4, 100, 24};
    auto rects = computeSegmentRects(h, 2);
    CHECK(rects.size() == 2);
    CHECK((rects[0] == SegmentRect{10, 4, 50, 24}));
    CHECK((rects[1] == SegmentRect{60, 4, 50, 24}));
}

// --- Layout: N=3, uneven width absorbed at boundaries ------------------------

// 100 wide / 3 doesn't divide evenly: boundaries at floor(i*100/3) =
// 0, 33, 66, 100 -> widths 33, 33, 34. The rounding lands in the LAST segment;
// segments still abut exactly and the last reaches header.x + width (0+100).
static void testThreeSegmentsUnevenTilesExactly() {
    HeaderRect h{0, 0, 100, 20};
    auto rects = computeSegmentRects(h, 3);
    CHECK(rects.size() == 3);
    CHECK((rects[0] == SegmentRect{0, 0, 33, 20}));
    CHECK((rects[1] == SegmentRect{33, 0, 33, 20}));
    CHECK((rects[2] == SegmentRect{66, 0, 34, 20}));
    // Adjacent segments abut (no gap, no overlap): each left edge == prior right.
    CHECK(rects[1].x == rects[0].x + rects[0].width);
    CHECK(rects[2].x == rects[1].x + rects[1].width);
    // Last segment's right edge reaches the header's right edge exactly.
    CHECK(rects[2].x + rects[2].width == h.x + h.width);
}

// --- Layout: full coverage invariant for a range of widths -------------------

// For any width and count, the segments must tile [x, x+width) with no gap/overlap:
// first left == x, last right == x+width, every mid boundary shared.
static void testTilingCoversHeaderForVariousWidths() {
    for (int width = 1; width <= 200; ++width) {
        for (int count = 1; count <= 5; ++count) {
            HeaderRect h{7, 3, width, 16};
            auto rects = computeSegmentRects(h, count);
            CHECK(static_cast<int>(rects.size()) == count);
            CHECK(rects.front().x == h.x);
            CHECK(rects.back().x + rects.back().width == h.x + h.width);
            for (std::size_t i = 1; i < rects.size(); ++i)
                CHECK(rects[i].x == rects[i - 1].x + rects[i - 1].width);
        }
    }
}

// --- Layout: degenerate --------------------------------------------------------

// segmentCount <= 0 or non-positive width yields no rects (the panel draws nothing
// / falls back to full-client grid).
static void testLayoutDegenerate() {
    CHECK(computeSegmentRects(HeaderRect{0, 0, 100, 20}, 0).empty());
    CHECK(computeSegmentRects(HeaderRect{0, 0, 100, 20}, -3).empty());
    CHECK(computeSegmentRects(HeaderRect{0, 0, 0, 20}, 2).empty());
    CHECK(computeSegmentRects(HeaderRect{0, 0, -5, 20}, 2).empty());
}

// A single segment fills the whole header.
static void testSingleSegmentFillsHeader() {
    HeaderRect h{5, 5, 80, 24};
    auto rects = computeSegmentRects(h, 1);
    CHECK(rects.size() == 1);
    CHECK((rects[0] == SegmentRect{5, 5, 80, 24}));
}

// --- Hit-test: hits -----------------------------------------------------------

// A point inside each segment maps to that segment's index. Header 100 wide at
// x=10, two segments [10,60) and [60,110).
static void testHitTestHitsEachSegment() {
    HeaderRect h{10, 4, 100, 24};
    CHECK(hitTestSegment(10, 4, h, 2) == 0);    // top-left corner of segment 0
    CHECK(hitTestSegment(35, 15, h, 2) == 0);   // middle of segment 0
    CHECK(hitTestSegment(59, 27, h, 2) == 0);   // last pixel of segment 0 (band)
    CHECK(hitTestSegment(60, 15, h, 2) == 1);   // first pixel of segment 1
    CHECK(hitTestSegment(109, 4, h, 2) == 1);   // last pixel column of segment 1
}

// The boundary pixel belongs to exactly ONE segment (half-open): px==60 is the
// start of segment 1, never the end of segment 0 — so no pixel is double-claimed.
static void testHitTestBoundaryHalfOpen() {
    HeaderRect h{10, 4, 100, 24};
    CHECK(hitTestSegment(59, 10, h, 2) == 0);
    CHECK(hitTestSegment(60, 10, h, 2) == 1);
    // Three-way boundaries at floor(i*100/2)+x are consistent for N=3 too.
    HeaderRect h3{0, 0, 99, 20};   // 99/3 = 33 exactly -> edges 0,33,66,99
    CHECK(hitTestSegment(32, 5, h3, 3) == 0);
    CHECK(hitTestSegment(33, 5, h3, 3) == 1);
    CHECK(hitTestSegment(65, 5, h3, 3) == 1);
    CHECK(hitTestSegment(66, 5, h3, 3) == 2);
    CHECK(hitTestSegment(98, 5, h3, 3) == 2);
}

// --- Hit-test: misses ---------------------------------------------------------

// Points outside the header band on any side miss (-1). Below the band is where
// the grid lives; the panel treats -1 as "fall through to grid handling".
static void testHitTestMissesOutsideBand() {
    HeaderRect h{10, 4, 100, 24};
    CHECK(hitTestSegment(9, 10, h, 2) == -1);    // left of header
    CHECK(hitTestSegment(110, 10, h, 2) == -1);  // right edge (px == x+width, excluded)
    CHECK(hitTestSegment(50, 3, h, 2) == -1);    // above the band
    CHECK(hitTestSegment(50, 28, h, 2) == -1);   // below the band (into the grid)
    CHECK(hitTestSegment(200, 200, h, 2) == -1); // far away
}

// Degenerate hit-test inputs miss safely.
static void testHitTestDegenerate() {
    HeaderRect h{10, 4, 100, 24};
    CHECK(hitTestSegment(50, 15, h, 0) == -1);   // no segments
    CHECK(hitTestSegment(50, 15, h, -2) == -1);
    CHECK(hitTestSegment(50, 15, HeaderRect{10, 4, 0, 24}, 2) == -1);   // zero width
    CHECK(hitTestSegment(50, 15, HeaderRect{10, 4, 100, 0}, 2) == -1);  // zero height
}

// --- Cross-check: every point in the header hit-tests to the segment that DREW
// it (hit-test and layout agree, the load-bearing consistency invariant). ------

static void testHitTestMatchesLayout() {
    HeaderRect h{4, 2, 87, 18};   // deliberately awkward width/origin
    const int count = 3;
    auto rects = computeSegmentRects(h, count);
    for (int px = h.x; px < h.x + h.width; ++px) {
        const int seg = hitTestSegment(px, h.y + 1, h, count);
        CHECK(seg >= 0);
        // The returned segment's rect must contain px (half-open).
        const SegmentRect& r = rects[static_cast<std::size_t>(seg)];
        CHECK(px >= r.x && px < r.x + r.width);
    }
}

int main() {
    testTwoSegmentsEvenSplit();
    testThreeSegmentsUnevenTilesExactly();
    testTilingCoversHeaderForVariousWidths();
    testLayoutDegenerate();
    testSingleSegmentFillsHeader();

    testHitTestHitsEachSegment();
    testHitTestBoundaryHalfOpen();
    testHitTestMissesOutsideBand();
    testHitTestDegenerate();
    testHitTestMatchesLayout();

    if (g_fail == 0) std::printf("All tests passed.\n");
    return g_fail ? 1 : 0;
}
