import json
import os
import tempfile

import FreeCAD as App


def version_text():
    return ".".join(str(value) for value in App.Version()[:3])


def placement_json(placement):
    axis = placement.Rotation.Axis
    base = placement.Base
    return {
        "position": [float(base.x), float(base.y), float(base.z)],
        "axis": [float(axis.x), float(axis.y), float(axis.z)],
        "angleDegrees": float(placement.Rotation.Angle * 180.0 / 3.141592653589793),
    }


def support_json(value):
    result = []
    for obj, sub_elements in value:
        subs = [sub_elements] if isinstance(sub_elements, str) else list(sub_elements)
        result.append({
            "document": obj.Document.Name,
            "object": obj.Name,
            "subElements": [str(sub) for sub in subs if str(sub)],
        })
    return result


def engine_json(obj):
    attacher = obj.Attacher
    implemented = list(attacher.ImplementedModes)
    return {
        "typeId": obj.TypeId,
        "attacherType": attacher.AttacherType,
        "completeModeList": list(attacher.CompleteModeList),
        "implementedModes": implemented,
        "modeInfo": {
            mode: {
                "modeIndex": int(attacher.getModeInfo(mode)["ModeIndex"]),
                "referenceCombinations": attacher.getModeInfo(mode)["ReferenceCombinations"],
            }
            for mode in implemented
        },
    }


def object_state(obj):
    suggestion = obj.Attacher.suggestModes()
    return {
        "typeId": obj.TypeId,
        "mapMode": str(obj.MapMode),
        "support": support_json(obj.AttachmentSupport),
        "placement": placement_json(obj.Placement),
        "state": [str(value) for value in obj.State],
        "status": str(obj.getStatusString()),
        "positionBySupport": bool(obj.positionBySupport()),
        "suggestion": {
            "message": suggestion["message"],
            "bestFitMode": suggestion["bestFitMode"],
            "allApplicableModes": suggestion["allApplicableModes"],
            "referenceTypes": suggestion["references_Types"],
        },
    }


document = App.newDocument("AttachmentModeOracle")
source_body = document.addObject("PartDesign::Body", "SourceBody")
source_box = source_body.newObject("PartDesign::AdditiveBox", "SourceBox")
source_box.Length = 4
source_box.Width = 5
source_box.Height = 6
document.recompute()

plane_body = document.addObject("PartDesign::Body", "PlaneBody")
datum_plane = plane_body.newObject("PartDesign::Plane", "DatumPlane")
datum_plane.AttachmentSupport = [(source_box, "Face1")]
datum_plane.MapMode = "FlatFace"

line_body = document.addObject("PartDesign::Body", "LineBody")
datum_line = line_body.newObject("PartDesign::Line", "DatumLine")
datum_line.AttachmentSupport = [(source_box, ("Vertex1", "Vertex2"))]
datum_line.MapMode = "TwoPointLine"

point_body = document.addObject("PartDesign::Body", "PointBody")
datum_point = point_body.newObject("PartDesign::Point", "DatumPoint")
datum_point.AttachmentSupport = [(source_box, "Vertex1")]
datum_point.MapMode = "Vertex"

sketch_body = document.addObject("PartDesign::Body", "SketchBody")
sketch = sketch_body.newObject("Sketcher::SketchObject", "Sketch")
sketch.AttachmentSupport = [(source_box, "Face1")]
sketch.MapMode = "FlatFace"

invalid_plane_body = document.addObject("PartDesign::Body", "InvalidPlaneBody")
invalid_plane = invalid_plane_body.newObject("PartDesign::Plane", "InvalidPlane")
invalid_plane.AttachmentSupport = [(source_box, "Vertex1")]
invalid_plane.MapMode = "Vertex"

invalid_line_body = document.addObject("PartDesign::Body", "InvalidLineBody")
invalid_line = invalid_line_body.newObject("PartDesign::Line", "InvalidLine")
invalid_line.AttachmentSupport = [(source_box, "Face1")]
invalid_line.MapMode = "FlatFace"

invalid_point_body = document.addObject("PartDesign::Body", "InvalidPointBody")
invalid_point = invalid_point_body.newObject("PartDesign::Point", "InvalidPoint")
invalid_point.AttachmentSupport = [(source_box, "Face1")]
invalid_point.MapMode = "FlatFace"

document.recompute()
engine_objects = {
    "plane": datum_plane,
    "line": datum_line,
    "point": datum_point,
    "sketch": sketch,
}
engines = {name: engine_json(obj) for name, obj in engine_objects.items()}
success_initial = {name: object_state(obj) for name, obj in engine_objects.items()}
failures = {
    obj.Name: {
        "typeId": obj.TypeId,
        "mapMode": str(obj.MapMode),
        "support": support_json(obj.AttachmentSupport),
        "state": [str(value) for value in obj.State],
        "status": str(obj.getStatusString()),
    }
    for obj in [invalid_plane, invalid_line, invalid_point]
}

source_box.Height = 9
document.recompute()
success_edited = {name: object_state(obj) for name, obj in engine_objects.items()}

with tempfile.TemporaryDirectory(prefix="freecad-attachment-mode-") as temp_dir:
    path = os.path.join(temp_dir, "AttachmentModeOracle.FCStd")
    document.saveAs(path)
    App.closeDocument(document.Name)
    reopened = App.openDocument(path)
    reopened.recompute()
    roundtrip = {
        name: object_state(reopened.getObject(object_name))
        for name, object_name in {
            "plane": "DatumPlane",
            "line": "DatumLine",
            "point": "DatumPoint",
            "sketch": "Sketch",
        }.items()
    }
    roundtrip_failures = {
        object_name: {
            "mapMode": str(reopened.getObject(object_name).MapMode),
            "state": [str(value) for value in reopened.getObject(object_name).State],
            "status": str(reopened.getObject(object_name).getStatusString()),
        }
        for object_name in ["InvalidPlane", "InvalidLine", "InvalidPoint"]
    }
    App.closeDocument(reopened.Name)

all_modes = engines["plane"]["completeModeList"]
implemented_union = sorted({mode for engine in engines.values() for mode in engine["implementedModes"]})
report = {
    "schemaVersion": 1,
    "baselineId": "freecad-1.1.1-attachment-mode-oracle",
    "freecadVersion": version_text(),
    "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d",
    "status": "pass",
    "tolerance": 1e-7,
    "registry": {
        "modeCount": len(all_modes),
        "modes": all_modes,
        "implementedUnionCount": len(implemented_union),
        "unimplementedModes": [mode for mode in all_modes if mode not in implemented_union],
    },
    "engines": engines,
    "success": {"initial": success_initial, "edited": success_edited, "roundtrip": roundtrip},
    "failures": {"initial": failures, "roundtrip": roundtrip_failures},
}
print("FREECAD_ATTACHMENT_MODE_RESULT=" + json.dumps(report, sort_keys=True, separators=(",", ":")))
