import json

import FreeCAD as App
import Part
import Sketcher


LENGTH = 10.0
TOLERANCE = 1e-7


def line(start, end):
    return Part.LineSegment(App.Vector(start[0], start[1], 0), App.Vector(end[0], end[1], 0))


def add_loop(sketch, points, closed=True):
    segment_count = len(points) if closed else len(points) - 1
    sketch.addGeometry([line(points[index], points[(index + 1) % len(points)]) for index in range(segment_count)], False)


def object_status(feature):
    try:
        status_string = feature.getStatusString()
    except Exception:
        status_string = ""
    return {
        "state": [str(value) for value in feature.State],
        "statusString": str(status_string),
    }


def run_case(case_id, expected, setup):
    document = App.newDocument("ProfileOracle_" + case_id.replace("-", "_"))
    try:
        body = document.addObject("PartDesign::Body", "Body")
        sketch = document.addObject("Sketcher::SketchObject", "Sketch")
        body.addObject(sketch)
        sketch.MapMode = "Deactivated"
        setup(sketch)
        document.recompute()

        pad = document.addObject("PartDesign::Pad", "Pad")
        pad.Profile = sketch
        pad.Length = LENGTH
        body.addObject(pad)
        recompute_result = document.recompute()
        shape_null = bool(pad.Shape.isNull())
        volume = 0.0 if shape_null else float(pad.Shape.Volume)
        solids = 0 if shape_null else len(pad.Shape.Solids)
        accepted = not shape_null and volume > TOLERANCE and solids >= 1
        observed = "accepted" if accepted else "rejected"
        return {
            "id": case_id,
            "expected": expected,
            "observed": observed,
            "passed": observed == expected,
            "recomputeResult": bool(recompute_result),
            "shapeNull": shape_null,
            "solidCount": int(solids),
            "volume": volume,
            **object_status(pad),
        }
    except Exception as error:
        return {
            "id": case_id,
            "expected": expected,
            "observed": "rejected",
            "passed": expected == "rejected",
            "errorType": type(error).__name__,
            "error": str(error),
        }
    finally:
        App.closeDocument(document.Name)


OUTER = [(0, 0), (6, 0), (6, 6), (0, 6)]
HOLE = [(2, 2), (2, 4), (4, 4), (4, 2)]


def closed_profile(sketch):
    add_loop(sketch, OUTER)


def multi_ring_profile(sketch):
    add_loop(sketch, HOLE)
    add_loop(sketch, OUTER)


def open_profile(sketch):
    add_loop(sketch, OUTER, False)


def self_intersecting_profile(sketch):
    add_loop(sketch, [(0, 0), (4, 4), (0, 4), (4, 0)])


def disjoint_profile(sketch):
    add_loop(sketch, OUTER)
    add_loop(sketch, [(8, 8), (9, 8), (9, 9), (8, 9)])


cases = [
    run_case("closed", "accepted", closed_profile),
    run_case("multi-ring", "accepted", multi_ring_profile),
    run_case("open", "rejected", open_profile),
    run_case("self-intersecting", "accepted", self_intersecting_profile),
    run_case("invalid-nesting", "accepted", disjoint_profile),
]
report = {
    "schemaVersion": 1,
    "baselineId": "freecad-1.1.1-partdesign-profile-oracle",
    "freecadVersion": ".".join(App.Version()[0:3]),
    "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d",
    "length": LENGTH,
    "tolerance": TOLERANCE,
    "status": "pass" if all(case["passed"] for case in cases) else "failed",
    "summary": {
        "cases": len(cases),
        "passed": sum(1 for case in cases if case["passed"]),
        "accepted": sum(1 for case in cases if case["observed"] == "accepted"),
        "rejected": sum(1 for case in cases if case["observed"] == "rejected"),
    },
    "cases": cases,
}
print("FREECAD_PARTDESIGN_PROFILE_ORACLE_RESULT=" + json.dumps(report, sort_keys=True))
