# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2023 Yorik van Havre <yorik@uncreated.net>              *
# *                                                                         *
# *   This file is part of FreeCAD.                                         *
# *                                                                         *
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
# *   under the terms of the GNU Lesser General Public License as           *
# *   published by the Free Software Foundation, either version 2.1 of the  *
# *   License, or (at your option) any later version.                       *
# *                                                                         *
# *   FreeCAD is distributed in the hope that it will be useful, but        *
# *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
# *   Lesser General Public License for more details.                       *
# *                                                                         *
# *   You should have received a copy of the GNU Lesser General Public      *
# *   License along with FreeCAD. If not, see                               *
# *   <https://www.gnu.org/licenses/>.                                      *
# *                                                                         *
# ***************************************************************************

"""This module contains geometry editing and geometry properties-related tools"""

import ifcopenshell
import ifcopenshell.util.unit

import FreeCAD

from . import ifc_tools


def add_geom_properties(obj):
    """Adds geometry properties to a FreeCAD object"""

    element = ifc_tools.get_ifc_element(obj)
    if not ifc_tools.has_representation(element):
        return
    ifcfile = ifc_tools.get_ifcfile(obj)
    scaling = ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
    scaling = scaling * 1000  # given scale is for m, we work in mm
    for rep in element.Representation.Representations:
        if rep.RepresentationIdentifier == "Body":
            if len(rep.Items) == 1:
                # Extrusions
                if rep.Items[0].is_a("IfcExtrudedAreaSolid"):
                    ext = rep.Items[0]
                    if "ExtrusionDepth" not in obj.PropertiesList:
                        obj.addProperty(
                            "App::PropertyLength", "ExtrusionDepth", "Geometry", locked=True
                        )
                    obj.ExtrusionDepth = ext.Depth * scaling
                    if "ExtrusionDirection" not in obj.PropertiesList:
                        obj.addProperty(
                            "App::PropertyVector", "ExtrusionDirection", "Geometry", locked=True
                        )
                    obj.ExtrusionDirection = FreeCAD.Vector(ext.ExtrudedDirection.DirectionRatios)

                    # Extrusion of a rectangle
                    if ext.SweptArea.is_a("IfcRectangleProfileDef"):
                        if "RectangleLength" not in obj.PropertiesList:
                            obj.addProperty(
                                "App::PropertyLength", "RectangleLength", "Geometry", locked=True
                            )
                        obj.RectangleLength = ext.SweptArea.XDim * scaling
                        if "RectangleWidth" not in obj.PropertiesList:
                            obj.addProperty(
                                "App::PropertyLength", "RectangleWidth", "Geometry", locked=True
                            )
                        obj.RectangleWidth = ext.SweptArea.YDim * scaling

                    # Extrusion of a polyline
                    elif ext.SweptArea.is_a("IfcArbitraryClosedProfileDef"):
                        if ext.SweptArea.OuterCurve.is_a("IfcPolyline"):
                            if "PolylinePoints" not in obj.PropertiesList:
                                obj.addProperty(
                                    "App::PropertyVectorList",
                                    "PolylinePoints",
                                    "Geometry",
                                    locked=True,
                                )
                            points = [p.Coordinates for p in ext.SweptArea.OuterCurve.Points]
                            points = [p + (0,) for p in points if len(p) < 3]
                            points = [FreeCAD.Vector(p).multiply(scaling) for p in points]
                            obj.PolylinePoints = points

                    # I profile
                    elif ext.SweptArea.is_a("IfcIShapeProfileDef"):
                        for p in [
                            "FilletRadius",
                            "FlangeEdgeRadius",
                            "FlangeSlope",
                            "FlangeThickness",
                            "OverallDepth",
                            "OverallWidth",
                            "WebThickness",
                        ]:
                            if hasattr(ext.SweptArea, p):
                                obj.addProperty("App::PropertyLength", p, "Geometry", locked=True)
                                value = getattr(ext.SweptArea, p)
                                if not value:
                                    value = 0
                                value = value * scaling
                                setattr(obj, p, value)
                            obj.addProperty(
                                "App::PropertyString", "ProfileName", "Geometry", locked=True
                            )
                            obj.ProfileName = ext.SweptArea.ProfileName

        # below is disabled for now... Don't know if it's useful to expose to the user
        elif False:  # rep.RepresentationIdentifier == "Axis":
            # Wall axis consisting on a single line
            if len(rep.Items) == 1:
                if rep.Items[0].is_a("IfcCompositeCurve"):
                    if len(rep.Items[0].Segments) == 1:
                        if rep.Items[0].Segments[0].is_a("IfcCompositeCurveSegment"):
                            if rep.Items[0].Segments[0].ParentCurve.is_a("IfcPolyline"):
                                pol = rep.Items[0].Segments[0].ParentCurve
                                if len(pol.Points) == 2:
                                    if "AxisStart" not in obj.PropertiesList:
                                        obj.addProperty(
                                            "App::PropertyPosition",
                                            "AxisStart",
                                            "Geometry",
                                            locked=True,
                                        )
                                    obj.AxisStart = FreeCAD.Vector(
                                        pol.Points[0].Coordinates
                                    ).multiply(scaling)
                                    if "AxisEnd" not in obj.PropertiesList:
                                        obj.addProperty(
                                            "App::PropertyPosition",
                                            "AxisEnd",
                                            "Geometry",
                                            locked=True,
                                        )
                                    obj.AxisEnd = FreeCAD.Vector(
                                        pol.Points[1].Coordinates
                                    ).multiply(scaling)


def set_attribute(ifcfile, element, prop, value):
    """Sets an attribute. Returns True if the attribute was changed"""

    if value != getattr(element, prop):
        ifc_tools.api_run(
            "attribute.edit_attributes",
            ifcfile,
            product=element,
            attributes={prop: value},
        )
        return True
    return False


def set_geom_property(obj, prop):
    """Updates the internal IFC file with the given value"""

    # TODO verify if values are different than the stored value before changing!

    element = ifc_tools.get_ifc_element(obj)
    if not ifc_tools.has_representation(element):
        return False
    ifcfile = ifc_tools.get_ifcfile(obj)
    scaling = ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
    scaling = 0.001 / scaling
    changed = False

    if prop == "ExtrusionDepth":
        for rep in element.Representation.Representations:
            if rep.RepresentationIdentifier == "Body":
                if len(rep.Items) == 1:
                    if rep.Items[0].is_a("IfcExtrudedAreaSolid"):
                        elem = rep.Items[0]
                        value = getattr(obj, prop).Value * scaling
                        changed = set_attribute(ifcfile, elem, "Depth", value)

    elif prop == "ExtrusionDirection":
        for rep in element.Representation.Representations:
            if rep.RepresentationIdentifier == "Body":
                if len(rep.Items) == 1:
                    if rep.Items[0].is_a("IfcExtrudedAreaSolid"):
                        elem = rep.Items[0].ExtrudedDirection
                        value = tuple(getattr(obj, prop))
                        changed = set_attribute(ifcfile, elem, "DirectionRatios", value)

    elif prop == "RectangleLength":
        for rep in element.Representation.Representations:
            if rep.RepresentationIdentifier == "Body":
                if len(rep.Items) == 1:
                    if rep.Items[0].is_a("IfcExtrudedAreaSolid"):
                        elem = rep.Items[0].SweptArea
                        if elem.is_a("IfcRectangleProfileDef"):
                            value = getattr(obj, prop).Value * scaling
                            changed = set_attribute(ifcfile, elem, "XDim", value)

    elif prop == "RectangleWidth":
        for rep in element.Representation.Representations:
            if rep.RepresentationIdentifier == "Body":
                if len(rep.Items) == 1:
                    if rep.Items[0].is_a("IfcExtrudedAreaSolid"):
                        elem = rep.Items[0].SweptArea
                        if elem.is_a("IfcRectangleProfileDef"):
                            value = getattr(obj, prop).Value * scaling
                            changed = set_attribute(ifcfile, elem, "YDim", value)

    elif prop == "PolylinePoints":
        # TODO check values against existing
        for rep in element.Representation.Representations:
            if rep.RepresentationIdentifier == "Body":
                if len(rep.Items) == 1:
                    if rep.Items[0].is_a("IfcExtrudedAreaSolid"):
                        if rep.Items[0].SweptArea.is_a("IfcArbitraryClosedProfileDef"):
                            if rep.Items[0].SweptArea.OuterCurve.is_a("IfcPolyline"):
                                elem = rep.Items[0].SweptArea.OuterCurve
                                elem_points = elem.Points
                                psize = elem_points[0].Dim
                                points = getattr(obj, prop)
                                if len(points) > len(elem_points):
                                    for i in range(len(points) - len(elem_points)):
                                        p = ifc_tools.api_run(
                                            "root.create_entity",
                                            ifcfile,
                                            ifc_class="IfcCartesianPoint",
                                        )
                                        elem_points.append(p)
                                    elem.Points = elem_points
                                elif len(points) < len(elem_points):
                                    rest = []
                                    for i in range(len(elem_points) - len(points)):
                                        rest.append(elem_points.pop())
                                    elem.Points = elem_points
                                    for r in rest:
                                        ifc_tools.api_run("root.remove_product", ifcfile, product=r)
                                if len(points) == len(elem_points):
                                    for i in range(len(points)):
                                        v = FreeCAD.Vector(points[i]).multiply(scaling)
                                        coord = tuple(v)[:psize]
                                        ifc_tools.api_run(
                                            "attribute.edit_attributes",
                                            ifcfile,
                                            product=elem_points[i],
                                            attributes={"Coordinates": coord},
                                        )
                                    changed = True

    elif prop in [
        "FilletRadius",
        "FlangeEdgeRadius",
        "FlangeSlope",
        "FlangeThickness",
        "OverallDepth",
        "OverallWidth",
        "WebThickness",
    ]:
        for rep in element.Representation.Representations:
            if rep.RepresentationIdentifier == "Body":
                if len(rep.Items) == 1:
                    if rep.Items[0].SweptArea.is_a("IfcIShapeProfileDef"):
                        elem = rep.Items[0].SweptArea
                        value = getattr(obj, prop).Value * scaling
                        if value == 0 and getattr(elem, prop) is None:
                            value = None
                        changed = set_attribute(ifcfile, elem, prop, value)

    elif prop in ["ProfileName"]:
        for rep in element.Representation.Representations:
            if rep.RepresentationIdentifier == "Body":
                if len(rep.Items) == 1:
                    if rep.Items[0].SweptArea.is_a("IfcIShapeProfileDef"):
                        elem = rep.Items[0].SweptArea
                        value = obj.ProfileName
                        changed = set_attribute(ifcfile, elem, prop, value)

    if changed:
        FreeCAD.Console.PrintLog(
            "DEBUG: Changing prop"
            + obj.Label
            + ":"
            + str(prop)
            + "to"
            + str(getattr(obj, prop))
            + "\n"
        )
    return changed
