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

# ***************************************************************************
# *   Copyright (c) 2013 Yorik van Havre <yorik@uncreated.net>              *
# *   Copyright (c) 2019 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
# *   Copyright (c) 2025 FreeCAD Project Association                        *
# *                                                                         *
# *   This file is part of the FreeCAD CAx development system.              *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   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 Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with FreeCAD; if not, write to the Free Software        *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************

"""Unit tests for the Draft Workbench, object creation tests."""

## @package test_creation
# \ingroup drafttests
# \brief Unit tests for the Draft Workbench, object creation tests.

## \addtogroup drafttests
# @{

import math

import FreeCAD as App
import Draft
from FreeCAD import Vector
from drafttests import test_base
from draftutils.messages import _msg


class DraftCreation(test_base.DraftTestCaseDoc):
    """Test Draft creation functions."""

    def test_line(self):
        """Create a line."""
        operation = "Draft Line"
        _msg("  Test '{}'".format(operation))
        a = Vector(0, 0, 0)
        b = Vector(2, 0, 0)
        _msg("  a={0}, b={1}".format(a, b))
        obj = Draft.make_line(a, b)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_polyline(self):
        """Create a polyline."""
        operation = "Draft Wire"
        _msg("  Test '{}'".format(operation))
        a = Vector(0, 0, 0)
        b = Vector(2, 0, 0)
        c = Vector(2, 2, 0)
        _msg("  a={0}, b={1}".format(a, b))
        _msg("  c={}".format(c))
        obj = Draft.make_wire([a, b, c])
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_fillet(self):
        """Create two lines, and a fillet between them."""
        operation = "Draft Fillet"
        _msg("  Test '{}'".format(operation))
        a = Vector(0, 0, 0)
        b = Vector(8, 0, 0)
        c = Vector(8, 8, 0)
        _msg("  Lines")
        _msg("  a={0}, b={1}".format(a, b))
        _msg("  b={0}, c={1}".format(b, c))
        L1 = Draft.make_line(a, b)
        L2 = Draft.make_line(b, c)
        self.doc.recompute()

        radius = 4
        _msg("  Fillet")
        _msg("  radius={}".format(radius))
        obj = Draft.make_fillet([L1, L2], radius)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_circle(self):
        """Create a circle."""
        operation = "Draft Circle"
        _msg("  Test '{}'".format(operation))
        radius = 3
        _msg("  radius={}".format(radius))
        obj = Draft.make_circle(radius)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_arc(self):
        """Create a circular arc."""
        operation = "Draft Arc"
        _msg("  Test '{}'".format(operation))
        radius = 2
        start_angle = 0
        end_angle = 90
        _msg("  radius={}".format(radius))
        _msg("  startangle={0}, endangle={1}".format(start_angle, end_angle))
        obj = Draft.make_circle(radius, startangle=start_angle, endangle=end_angle)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_arc_3points(self):
        """Create a circular arc from three points."""
        operation = "Draft Arc 3Points"
        _msg("  Test '{}'".format(operation))
        a = Vector(5, 0, 0)
        b = Vector(4, 3, 0)
        c = Vector(0, 5, 0)
        _msg("  a={0}, b={1}".format(a, b))
        _msg("  c={}".format(c))

        obj = Draft.make_arc_3points([a, b, c])
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_ellipse(self):
        """Create an ellipse."""
        operation = "Draft Ellipse"
        _msg("  Test '{}'".format(operation))
        a = 5
        b = 3
        _msg("  major_axis={0}, minor_axis={1}".format(a, b))
        obj = Draft.make_ellipse(a, b)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_polygon(self):
        """Create a regular polygon."""
        operation = "Draft Polygon"
        _msg("  Test '{}'".format(operation))
        n_faces = 6
        radius = 5
        _msg("  n_faces={0}, radius={1}".format(n_faces, radius))
        obj = Draft.make_polygon(n_faces, radius)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_rectangle(self):
        """Create a rectangle."""
        operation = "Draft Rectangle"
        _msg("  Test '{}'".format(operation))
        length = 5
        width = 2
        _msg("  length={0}, width={1}".format(length, width))
        obj = Draft.make_rectangle(length, width)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_text(self):
        """Create a text object."""
        operation = "Draft Text"
        _msg("  Test '{}'".format(operation))
        text = "Testing testing"
        _msg("  text='{}'".format(text))
        obj = Draft.make_text(text)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_dimension_linear_simple(self):
        """Create a simple linear dimension not linked to an object."""
        operation = "Draft Dimension"
        _msg("  Test '{}'".format(operation))
        _msg("  Occasionally crashes")
        a = Vector(0, 0, 0)
        b = Vector(9, 0, 0)
        c = Vector(4, -1, 0)
        _msg("  a={0}, b={1}".format(a, b))
        _msg("  c={}".format(c))
        obj = Draft.make_linear_dimension(a, b, c)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_dimension_linear_obj(self):
        """Create a linear dimension linked to an object."""
        operation = "Draft Dimension"
        _msg("  Test '{}'".format(operation))
        _msg("  Occasionally crashes")
        a = Vector(0, 0, 0)
        b = Vector(9, 0, 0)
        _msg("  a={0}, b={1}".format(a, b))
        line = Draft.make_line(a, b)
        self.doc.recompute()

        obj = Draft.make_linear_dimension_obj(line, i1=1, i2=2, dim_line=Vector(5, 3, 0))
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_dimension_radial_obj(self):
        """Create a circle and then a radial and a diameter dimension."""
        operation = "Draft Dimension Radial"
        _msg("  Test '{}'".format(operation))
        radius = 10
        start_angle = 0
        end_angle = 90
        _msg("  radius={}".format(radius))
        _msg("  startangle={0}, endangle={1}".format(start_angle, end_angle))
        circ = Draft.make_circle(radius, startangle=start_angle, endangle=end_angle)
        self.doc.recompute()

        obj1 = Draft.make_radial_dimension_obj(
            circ, index=1, mode="radius", dim_line=Vector(1, 1, 0)
        )
        obj2 = Draft.make_radial_dimension_obj(
            circ, index=1, mode="diameter", dim_line=Vector(3, 1, 0)
        )
        self.assertTrue(obj1 and obj2, "'{}' failed".format(operation))

    def test_dimension_angular(self):
        """Create an angular dimension between two lines at given angles."""
        operation = "Draft Dimension Angular"
        _msg("  Test '{}'".format(operation))
        _msg("  Occasionally crashes")
        center = Vector(0, 0, 0)
        angle1 = 20
        angle2 = 70
        dim_line = Vector(3, 1, 0)
        _msg("  center={}".format(center))
        _msg("  angle1={0}, angle2={1}".format(angle1, angle2))
        _msg("  dim_line={}".format(dim_line))
        obj = Draft.make_angular_dimension(center, [angle1, angle2], dim_line)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_bspline(self):
        """Create a BSpline of three points."""
        operation = "Draft BSpline"
        _msg("  Test '{}'".format(operation))
        a = Vector(0, 0, 0)
        b = Vector(2, 0, 0)
        c = Vector(2, 2, 0)
        _msg("  a={0}, b={1}".format(a, b))
        _msg("  c={}".format(c))
        obj = Draft.make_bspline([a, b, c])
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_point(self):
        """Create a point."""
        operation = "Draft Point"
        _msg("  Test '{}'".format(operation))
        p = Vector(5, 3, 2)
        _msg("  p.x={0}, p.y={1}, p.z={2}".format(p.x, p.y, p.z))
        obj = Draft.make_point(p.x, p.y, p.z)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_shapestring(self):
        """Create a ShapeString."""
        operation = "Draft ShapeString"
        _msg("  Test '{}'".format(operation))
        text = "Test"
        fontfile = App.getResourceDir() + "Mod/TechDraw/Resources/fonts/osifont-lgpl3fe.ttf"
        _msg("  text='{0}'".format(text))
        _msg("  fontfile='{0}'".format(fontfile))
        obj = Draft.make_shapestring(text, fontfile)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_facebinder(self):
        """Create a box, and then a facebinder from its faces."""
        operation = "Draft Facebinder"
        _msg("  Test '{}'".format(operation))
        _msg("  In order to test this, a selection is needed")
        _msg("  or an App::PropertyLinkSubList")

        _msg("  Box")
        box = self.doc.addObject("Part::Box")
        self.doc.recompute()
        # The facebinder function accepts a Gui selection set,
        # or a 'PropertyLinkSubList'

        # Gui selection set only works when the graphical interface is up
        # Gui.Selection.addSelection(box, ('Face1', 'Face6'))
        # selection_set = Gui.Selection.getSelectionEx()
        # elements = selection_set[0].SubElementNames

        # PropertyLinkSubList
        selection_set = [(box, ("Face1", "Face6"))]
        elements = selection_set[0][1]
        _msg("  object='{0}' ({1})".format(box.Shape.ShapeType, box.TypeId))
        _msg("  sub-elements={}".format(elements))
        obj = Draft.make_facebinder(selection_set)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_cubicbezcurve(self):
        """Create a cubic bezier curve of four points."""
        operation = "Draft CubBezCurve"
        _msg("  Test '{}'".format(operation))
        a = Vector(0, 0, 0)
        b = Vector(2, 2, 0)
        c = Vector(5, 3, 0)
        d = Vector(9, 0, 0)
        _msg("  a={0}, b={1}".format(a, b))
        _msg("  c={0}, d={1}".format(c, d))
        obj = Draft.make_bezcurve([a, b, c, d], degree=3)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_bezcurve(self):
        """Create a bezier curve of six points, degree five."""
        operation = "Draft BezCurve"
        _msg("  Test '{}'".format(operation))
        a = Vector(0, 0, 0)
        b = Vector(2, 2, 0)
        c = Vector(5, 3, 0)
        d = Vector(9, 0, 0)
        e = Vector(12, 5, 0)
        f = Vector(12, 8, 0)
        _msg("  a={0}, b={1}".format(a, b))
        _msg("  c={0}, d={1}".format(c, d))
        _msg("  e={0}, f={1}".format(e, f))
        obj = Draft.make_bezcurve([a, b, c, d, e, f])
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_label(self):
        """Create a label."""
        operation = "Draft Label"
        _msg("  Test '{}'".format(operation))
        _msg("  Occasionally crashes")
        target_point = Vector(0, 0, 0)
        distance = -25
        placement = App.Placement(Vector(50, 50, 0), App.Rotation())
        _msg("  target_point={0}, " "distance={1}".format(target_point, distance))
        _msg("  placement={}".format(placement))
        obj = Draft.make_label(target_point=target_point, distance=distance, placement=placement)
        self.doc.recompute()
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_layer(self):
        """Create a layer, and add a rectangle to it."""
        operation = "Draft Layer"
        _msg("  Test '{}'".format(operation))
        length = 5
        width = 2
        _msg("  length={0}, width={1}".format(length, width))
        rect = Draft.make_rectangle(length, width)

        obj = Draft.make_layer()
        obj.Proxy.addObject(obj, rect)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_workingplaneproxy(self):
        """Create a working plane proxy."""
        operation = "Draft WorkingPlaneProxy"
        _msg("  Test '{}'".format(operation))
        placement = App.Placement(Vector(10, 20, 0), App.Rotation())
        _msg("  placement={}".format(placement))
        obj = Draft.make_workingplaneproxy(placement)
        self.assertTrue(obj, "'{}' failed".format(operation))

    def test_hatch(self):
        """Create a hatch."""
        operation = "Draft Hatch"
        _msg("  Test '{}'".format(operation))
        length = 50
        width = 30
        _msg("  Rectangle with face")
        _msg("  length={0}, width={1}".format(length, width))
        rect = Draft.make_rectangle(length, width)
        rect.MakeFace = True
        self.doc.recompute()

        patfile = App.getResourceDir() + "Mod/TechDraw/PAT/FCPAT.pat"
        patname = "Horizontal5"
        _msg("  patfile='{0}'".format(patfile))
        _msg("  patname='{0}'".format(patname))
        obj = Draft.make_hatch(rect, patfile, patname, scale=1, rotation=45)
        self.doc.recompute()

        box = obj.Shape.BoundBox
        # A rather high tolerance is required.
        obj_is_ok = (
            box.Center.isEqual(Vector(length / 2, width / 2, 0), 1e-6)
            and math.isclose(box.XLength, length, rel_tol=0, abs_tol=1e-6)
            and math.isclose(box.YLength, width, rel_tol=0, abs_tol=1e-6)
        )
        self.assertTrue(obj_is_ok, "'{}' failed".format(operation))


## @}
