// ok cpp include
// g++ --std=c++11 test.cpp


#include "fl/math/screenmap.h"
#include "fl/stl/new.h"
#include "test.h"
#include "fl/math/geometry.h"
#include "fl/stl/map.h"
#include "fl/stl/move.h"
#include "fl/stl/string.h"
#include "fl/stl/utility.h"
using namespace fl;
using fl::string;

FL_TEST_CASE("ScreenMap basic functionality") {
    // Create a screen map for 3 LEDs
    ScreenMap map(3);
    
    // Set some x,y coordinates
    map.set(0, {1.0f, 2.0f});
    map.set(1, {3.0f, 4.0f});
    map.set(2, {5.0f, 6.0f});
    
    // Test coordinate retrieval
    FL_CHECK(map[0].x == 1.0f);
    FL_CHECK(map[0].y == 2.0f);
    FL_CHECK(map[1].x == 3.0f);
    FL_CHECK(map[1].y == 4.0f);
    FL_CHECK(map[2].x == 5.0f);
    FL_CHECK(map[2].y == 6.0f);
    
    // Test length
    FL_CHECK(map.getLength() == 3);
    
    // Test diameter (default should be -1.0)
    FL_CHECK(map.getDiameter() == -1.0f);
    
    // Test mapToIndex (should give same results as operator[])
    auto coords = map.mapToIndex(1);
    FL_CHECK(coords.x == 3.0f);
    FL_CHECK(coords.y == 4.0f);
}

FL_TEST_CASE("ScreenMap JSON parsing") {
    const char* json = R"({
        "map": {
            "strip1": {
                "x": [10.5, 30.5, 50.5],
                "y": [20.5, 40.5, 60.5],
                "diameter": 2.5
            },
            "strip2": {
                "x": [15.0, 35.0],
                "y": [25.0, 45.0],
                "diameter": 1.5
            }
            
        }
    })";

    fl::flat_map<string, ScreenMap> segmentMaps;
    ScreenMap::ParseJson(json, &segmentMaps);

    ScreenMap& strip1 = segmentMaps["strip1"];
    ScreenMap& strip2 = segmentMaps["strip2"];

    // Check first strip

    FL_CHECK(strip1.getLength() == 3);
    FL_CHECK(strip1.getDiameter() == 2.5f);
    FL_CHECK(strip1[0].x == 10.5f);
    FL_CHECK(strip1[0].y == 20.5f);
    FL_CHECK(strip1[1].x == 30.5f);
    FL_CHECK(strip1[1].y == 40.5f);

    // Check second strip
    FL_CHECK(strip2.getLength() == 2);
    FL_CHECK(strip2.getDiameter() == 1.5f);
    FL_CHECK(strip2[0].x == 15.0f);
    FL_CHECK(strip2[0].y == 25.0f);
    FL_CHECK(strip2[1].x == 35.0f);
    FL_CHECK(strip2[1].y == 45.0f);
}

FL_TEST_CASE("ScreenMap multiple strips JSON serialization") {
    // Create a map with multiple strips
    fl::flat_map<fl::string, ScreenMap> originalMaps;
    
    // First strip
    ScreenMap strip1(2, 2.0f);
    strip1.set(0, {1.0f, 2.0f});
    strip1.set(1, {3.0f, 4.0f});
    originalMaps["strip1"] = strip1;
    
    // Second strip
    ScreenMap strip2(3, 1.5f);
    strip2.set(0, {10.0f, 20.0f});
    strip2.set(1, {30.0f, 40.0f});
    strip2.set(2, {50.0f, 60.0f});
    originalMaps["strip2"] = strip2;

    // Serialize to JSON string
    fl::string jsonStr;
    ScreenMap::toJsonStr(originalMaps, &jsonStr);

    // Deserialize back to a new map
    fl::flat_map<fl::string, ScreenMap> deserializedMaps;
    ScreenMap::ParseJson(jsonStr.c_str(), &deserializedMaps);

    // Verify first strip
    ScreenMap& deserializedStrip1 = deserializedMaps["strip1"];
    FL_CHECK(deserializedStrip1.getLength() == 2);
    FL_CHECK(deserializedStrip1.getDiameter() == 2.0f);
    FL_CHECK(deserializedStrip1[0].x == 1.0f);
    FL_CHECK(deserializedStrip1[0].y == 2.0f);
    FL_CHECK(deserializedStrip1[1].x == 3.0f);
    FL_CHECK(deserializedStrip1[1].y == 4.0f);

    // Verify second strip
    ScreenMap& deserializedStrip2 = deserializedMaps["strip2"];
    FL_CHECK(deserializedStrip2.getLength() == 3);
    FL_CHECK(deserializedStrip2.getDiameter() == 1.5f);
    FL_CHECK(deserializedStrip2[0].x == 10.0f);
    FL_CHECK(deserializedStrip2[0].y == 20.0f);
    FL_CHECK(deserializedStrip2[1].x == 30.0f);
    FL_CHECK(deserializedStrip2[1].y == 40.0f);
    FL_CHECK(deserializedStrip2[2].x == 50.0f);
    FL_CHECK(deserializedStrip2[2].y == 60.0f);
}

FL_TEST_CASE("ScreenMap getBounds functionality") {
    // Create a screen map with points at different coordinates
    ScreenMap map(4);
    map.set(0, {1.0f, 2.0f});
    map.set(1, {-3.0f, 4.0f});
    map.set(2, {5.0f, -6.0f});
    map.set(3, {-2.0f, -1.0f});
    
    // Get the bounds
    vec2f bounds = map.getBounds();
    
    // The bounds should be the difference between max and min values
    // Max X: 5.0, Min X: -3.0 => Width = 8.0
    // Max Y: 4.0, Min Y: -6.0 => Height = 10.0
    FL_CHECK(bounds.x == 8.0f);
    FL_CHECK(bounds.y == 10.0f);
    
    // Test with a single point
    ScreenMap singlePoint(1);
    singlePoint.set(0, {3.5f, 4.5f});
    vec2f singleBounds = singlePoint.getBounds();
    FL_CHECK(singleBounds.x == 0.0f);
    FL_CHECK(singleBounds.y == 0.0f);
    
    // Test with an empty map
    ScreenMap emptyMap(0);
    vec2f emptyBounds = emptyMap.getBounds();
    FL_CHECK(emptyBounds.x == 0.0f);
    FL_CHECK(emptyBounds.y == 0.0f);
}

FL_TEST_CASE("ScreenMap v2 JSON parsing — basic segments") {
    // v2: top-level `segments` array, each entry has its own x/y arrays.
    const char* json = R"({
        "version": 2,
        "groups": { "trunk": { "color": "#ffffff" } },
        "segments": [
            { "id": "a", "pin": 1, "group": "trunk",
              "x": [0.0, 1.0, 2.0], "y": [0.0, 0.0, 0.0] },
            { "id": "b", "pin": 2, "group": "trunk",
              "x": [10.0, 11.0], "y": [5.0, 5.0] }
        ]
    })";

    fl::flat_map<string, ScreenMap> segmentMaps;
    string err;
    bool ok = ScreenMap::ParseJson(json, &segmentMaps, &err);
    FL_CHECK(ok);
    FL_CHECK(segmentMaps.size() == 2);

    ScreenMap& a = segmentMaps["a"];
    FL_CHECK(a.getLength() == 3);
    FL_CHECK(a[0].x == 0.0f); FL_CHECK(a[0].y == 0.0f);
    FL_CHECK(a[1].x == 1.0f); FL_CHECK(a[1].y == 0.0f);
    FL_CHECK(a[2].x == 2.0f); FL_CHECK(a[2].y == 0.0f);

    ScreenMap& b = segmentMaps["b"];
    FL_CHECK(b.getLength() == 2);
    FL_CHECK(b[0].x == 10.0f); FL_CHECK(b[0].y == 5.0f);
    FL_CHECK(b[1].x == 11.0f); FL_CHECK(b[1].y == 5.0f);
}

FL_TEST_CASE("ScreenMap v2 JSON parsing — auto-detect without explicit version") {
    // Same shape as the test above but no "version" key — parser should still
    // route to v2 because the root has a `segments` array.
    const char* json = R"({
        "segments": [
            { "id": "x", "pin": 1, "group": "g",
              "x": [0.5, 1.5], "y": [2.5, 3.5] }
        ]
    })";

    fl::flat_map<string, ScreenMap> segmentMaps;
    string err;
    bool ok = ScreenMap::ParseJson(json, &segmentMaps, &err);
    FL_CHECK(ok);
    FL_CHECK(segmentMaps.size() == 1);
    ScreenMap& x = segmentMaps["x"];
    FL_CHECK(x.getLength() == 2);
    FL_CHECK(x[0].x == 0.5f); FL_CHECK(x[0].y == 2.5f);
    FL_CHECK(x[1].x == 1.5f); FL_CHECK(x[1].y == 3.5f);
}

FL_TEST_CASE("ScreenMap v2 JSON parsing — forks produce separate entries") {
    // A fork is just another segment with the same shape; in firmware-flat form
    // each fork twin gets its own keyed entry under its `id`.
    const char* json = R"({
        "version": 2,
        "groups": { "trunk": { "color": "#fff" }, "branch": { "color": "#f00" } },
        "segments": [
            { "id": "a", "pin": 1, "group": "trunk",
              "x": [0.0, 1.0, 2.0, 3.0, 4.0],
              "y": [0.0, 0.0, 0.0, 0.0, 0.0] },
            { "id": "b", "pin": 2, "group": "branch", "parent": "a", "offset": 2,
              "x": [2.0, 2.0, 2.0],
              "y": [0.0, 1.0, 2.0] }
        ]
    })";

    fl::flat_map<string, ScreenMap> segmentMaps;
    string err;
    bool ok = ScreenMap::ParseJson(json, &segmentMaps, &err);
    FL_CHECK(ok);
    FL_CHECK(segmentMaps.size() == 2);

    ScreenMap& a = segmentMaps["a"];
    FL_CHECK(a.getLength() == 5);
    FL_CHECK(a[0].x == 0.0f); FL_CHECK(a[2].x == 2.0f); FL_CHECK(a[4].x == 4.0f);

    ScreenMap& b = segmentMaps["b"];
    FL_CHECK(b.getLength() == 3);
    FL_CHECK(b[0].x == 2.0f); FL_CHECK(b[0].y == 0.0f);
    FL_CHECK(b[2].x == 2.0f); FL_CHECK(b[2].y == 2.0f);
}

FL_TEST_CASE("ScreenMap v2 JSON parsing — v1 file with explicit version=1 still works") {
    // Explicit "version": 1 should NOT trip v2 dispatch. Existing "map" path runs.
    const char* json = R"({
        "version": 1,
        "map": {
            "strip1": { "x": [1.0, 2.0], "y": [3.0, 4.0], "diameter": 0.25 }
        }
    })";

    fl::flat_map<string, ScreenMap> segmentMaps;
    string err;
    bool ok = ScreenMap::ParseJson(json, &segmentMaps, &err);
    FL_CHECK(ok);
    FL_CHECK(segmentMaps.size() == 1);
    ScreenMap& s = segmentMaps["strip1"];
    FL_CHECK(s.getLength() == 2);
    FL_CHECK(s.getDiameter() == 0.25f);
    FL_CHECK(s[0].x == 1.0f); FL_CHECK(s[1].y == 4.0f);
}

FL_TEST_CASE("ScreenMap v2 JSON parsing — malformed segments errors cleanly") {
    // Missing 'x' should produce an informative error string and return false.
    const char* json = R"({
        "version": 2,
        "segments": [
            { "id": "broken", "pin": 1, "group": "g", "y": [0.0, 1.0] }
        ]
    })";

    fl::flat_map<string, ScreenMap> segmentMaps;
    string err;
    bool ok = ScreenMap::ParseJson(json, &segmentMaps, &err);
    FL_CHECK(!ok);
    FL_CHECK(segmentMaps.size() == 0);
    FL_CHECK(err.size() > 0);
}

// Grouped tests
#include "tests/fl/math/xymap.hpp"

FL_TEST_FILE(FL_FILEPATH) {

} // FL_TEST_FILE
