import json
import os
import sys

import FreeCAD as App
import FreeCADGui as Gui
import Part
import Sketcher


path = os.environ.get("FREECAD_FCSTD_PATH", "")
object_name = os.environ.get("FREECAD_FCSTD_OBJECT", "Box")
if not path:
    raise RuntimeError("FREECAD_FCSTD_PATH must point to an FCStd file.")

if os.environ.get("FREECAD_FCSTD_CREATE") == "1":
    generated = App.newDocument("GuiOracleGenerator")
    generated_object = generated.addObject(os.environ.get("FREECAD_FCSTD_CREATE_TYPEID", "Part::Feature"), object_name)
    for property_name, property_value in json.loads(os.environ.get("FREECAD_FCSTD_CREATE_PROPERTIES", "{}")).items():
        setattr(generated_object, property_name, property_value)
    generated_brep_path = os.environ.get("FREECAD_BREP_PATH", "")
    if generated_brep_path:
        generated_shape = Part.Shape()
        generated_shape.read(generated_brep_path)
        generated_object.Shape = generated_shape
    generated_view = Gui.getDocument(generated.Name).getObject(object_name)
    generated_view.Visibility = False
    generated_view.Transparency = 35
    generated_view.LineColor = (0x11 / 255.0, 0x22 / 255.0, 0x33 / 255.0)
    generated_view.PointColor = (0x44 / 255.0, 0x55 / 255.0, 0x66 / 255.0)
    generated_view.Deviation = 0.2
    generated.recompute()
    generated.saveAs(path)
    App.closeDocument(generated.Name)

if not os.path.isfile(path):
    raise RuntimeError("FREECAD_FCSTD_PATH does not exist.")

document = App.openDocument(path)
obj = document.getObject(object_name)
if obj is None:
    raise RuntimeError(f"FCStd object does not exist: {object_name}")
if os.environ.get("FREECAD_FCSTD_RECOMPUTE") == "1":
    document.recompute()
view = Gui.getDocument(document.Name).getObject(object_name)
if view is None:
    raise RuntimeError(f"FCStd ViewObject does not exist: {object_name}")

resave_path = os.environ.get("FREECAD_FCSTD_RESAVE_PATH", "")
if resave_path:
    document.saveAs(resave_path)

shape_result = {
    "shapeNull": None,
    "shapeValid": None,
    "solidCount": None,
    "faceCount": None,
    "volume": None,
    "boundingBox": None,
    "shapeError": None,
}
if hasattr(obj, "Shape"):
    try:
        shape_result.update({
            "shapeNull": bool(obj.Shape.isNull()),
            "shapeValid": bool(obj.Shape.isValid()) if not obj.Shape.isNull() else None,
            "solidCount": len(obj.Shape.Solids),
            "faceCount": len(obj.Shape.Faces),
            "volume": float(obj.Shape.Volume),
            "boundingBox": {
                "min": [float(obj.Shape.BoundBox.XMin), float(obj.Shape.BoundBox.YMin), float(obj.Shape.BoundBox.ZMin)],
                "max": [float(obj.Shape.BoundBox.XMax), float(obj.Shape.BoundBox.YMax), float(obj.Shape.BoundBox.ZMax)],
            },
        })
    except Exception as error:
        shape_result["shapeError"] = str(error)

direct_brep_result = None
direct_brep_path = os.environ.get("FREECAD_BREP_PATH", "")
if direct_brep_path:
    direct_brep_result = {"valid": None, "solidCount": None, "faceCount": None, "volume": None, "error": None}
    try:
        direct_shape = Part.Shape()
        direct_shape.read(direct_brep_path)
        direct_brep_result.update({
            "valid": bool(direct_shape.isValid()),
            "solidCount": len(direct_shape.Solids),
            "faceCount": len(direct_shape.Faces),
            "volume": float(direct_shape.Volume),
        })
    except Exception as error:
        direct_brep_result["error"] = str(error)

property_result = {}
for property_name in filter(None, os.environ.get("FREECAD_FCSTD_PROPERTIES", "").split(",")):
    try:
        property_value = getattr(obj, property_name)
        if hasattr(property_value, "Value"):
            property_result[property_name] = float(property_value.Value)
        elif hasattr(property_value, "Name"):
            property_result[property_name] = property_value.Name
        else:
            property_result[property_name] = property_value
    except Exception as error:
        property_result[property_name] = {"error": str(error)}

sketch_result = {
    "geometryCount": None,
    "constraintCount": None,
    "constructionGeometry": None,
    "constraintNames": None,
    "constraintTypes": None,
    "externalGeometryCount": None,
    "externalGeoCount": None,
    "externalTypes": None,
    "externalLinks": None,
    "externalStates": None,
    "mapMode": None,
    "attachmentSupport": None,
    "attachmentOffset": None,
}
if hasattr(obj, "GeometryCount") and hasattr(obj, "ConstraintCount"):
    try:
        sketch_result.update({
            "geometryCount": int(obj.GeometryCount),
            "constraintCount": int(obj.ConstraintCount),
            "constructionGeometry": [index for index in range(int(obj.GeometryCount)) if obj.getConstruction(index)],
            "constraintNames": [str(constraint.Name) for constraint in obj.Constraints],
            "constraintTypes": [str(constraint.Type) for constraint in obj.Constraints],
        })
    except Exception as error:
        sketch_result["error"] = str(error)
if hasattr(obj, "ExternalGeometry") and hasattr(obj, "ExternalGeo"):
    try:
        external_links = []
        for linked_object, sub_elements in obj.ExternalGeometry:
            external_links.append({
                "objectName": str(linked_object.Name),
                "subElements": [str(sub_element) for sub_element in sub_elements],
            })
        sketch_result.update({
            "externalGeometryCount": len(obj.ExternalGeometry),
            "externalGeoCount": len(obj.ExternalGeo),
            "externalTypes": [int(value) for value in obj.ExternalTypes],
            "externalLinks": external_links,
            "externalStates": [
                {
                    "ref": str(facade.Ref),
                    "id": int(facade.Id),
                    "geometryType": type(geometry).__name__,
                    "flags": sum(
                        (1 << index) if facade.testFlag(flag) else 0
                        for index, flag in enumerate(("Defining", "Frozen", "Detached", "Missing", "Sync"))
                    ),
                }
                for geometry in obj.ExternalGeo
                for facade in (Sketcher.ExternalGeometryFacade(geometry),)
                if facade.Ref
            ],
        })
    except Exception as error:
        sketch_result["externalError"] = str(error)
if hasattr(obj, "MapMode") and hasattr(obj, "AttachmentSupport") and hasattr(obj, "AttachmentOffset"):
    try:
        attachment_links = []
        for linked_object, sub_elements in obj.AttachmentSupport:
            attachment_links.append({
                "objectName": str(linked_object.Name),
                "subElements": [str(sub_element) for sub_element in sub_elements],
            })
        offset = obj.AttachmentOffset
        sketch_result.update({
            "mapMode": str(obj.MapMode),
            "attachmentSupport": attachment_links,
            "attachmentOffset": {
                "position": [float(offset.Base.x), float(offset.Base.y), float(offset.Base.z)],
                "axis": [float(offset.Rotation.Axis.x), float(offset.Rotation.Axis.y), float(offset.Rotation.Axis.z)],
                "angle": float(offset.Rotation.Angle),
            },
        })
    except Exception as error:
        sketch_result["attachmentError"] = str(error)

result = {
    "schemaVersion": 1,
    "freecadVersion": ".".join(str(value) for value in App.Version()[:3]),
    "guiUp": bool(getattr(App, "GuiUp", False)),
    "objectName": object_name,
    "visibility": bool(view.Visibility),
    "transparency": int(view.Transparency),
    "lineColor": [float(value) for value in view.LineColor],
    "pointColor": [float(value) for value in view.PointColor],
    "shapeColor": [float(value) for value in view.ShapeColor],
    "deviation": float(view.Deviation),
    "directBrep": direct_brep_result,
    "properties": property_result,
    **sketch_result,
    **shape_result,
}
App.closeDocument(document.Name)
print("FREECAD_FCSTD_GUI_RESULT=" + json.dumps(result, sort_keys=True, separators=(",", ":")))
sys.stdout.flush()
sys.exit(0)
