# ***************************************************************************
# *   Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
# *   Copyright (c) 2021 FreeCAD Developers                                 *
# *                                                                         *
# *   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                                                                   *
# *                                                                         *
# ***************************************************************************
"""Run this file to create a standard test document for Draft objects.

Use it as input to the program executable.

::

    freecad draft_test_objects.py

Or load it as a module and use the defined function.

>>> import drafttests.draft_test_objects as dt
>>> dt.create_test_file()
"""
## @package draft_test_objects
# \ingroup drafttests
# \brief Run this file to create a standard test document for Draft objects.

## \addtogroup drafttests
# @{
import datetime

import FreeCAD as App
import Part
import Draft

from draftutils.messages import _msg, _wrn
from FreeCAD import Vector

if App.GuiUp:
    import FreeCADGui as Gui


def _set_text(text_list, position):
    """Set a text annotation with properties."""
    obj = App.ActiveDocument.addObject("App::Annotation", "Annotation")
    obj.LabelText = text_list
    obj.Position = position

    if App.GuiUp:
        obj.ViewObject.DisplayMode = "World"
        obj.ViewObject.FontSize = 75
        obj.ViewObject.TextColor = (0.0, 0.0, 0.0)


def _create_frame(doc=None):
    """Draw a frame with information on the version of the software."""
    if not doc:
        doc = App.activeDocument()
    if not doc:
        doc = App.newDocument()

    version = App.Version()
    now = datetime.datetime.now().strftime("%Y/%m/%dT%H:%M:%S")

    _text = ["Draft test file",
             "Created: {}".format(now),
             "\n",
             "Version: " + ".".join(version[0:3]),
             "Release: " + " ".join(version[3:5]),
             "Branch: " + " ".join(version[5:])]

    record = doc.addObject("App::Annotation", "Description")
    record.LabelText = _text
    record.Position = Vector(0, -1000, 0)

    if App.GuiUp:
        record.ViewObject.DisplayMode = "World"
        record.ViewObject.FontSize = 400
        record.ViewObject.TextColor = (0.0, 0.0, 0.0)

    p1 = Vector(-1000, -4000, 0)
    p2 = Vector(17000, -4000, 0)
    p3 = Vector(17000, 8000, 0)
    p4 = Vector(-1000, 8000, 0)

    poly = Part.makePolygon([p1, p2, p3, p4, p1])
    frame = doc.addObject("Part::Feature", "Frame")
    frame.Shape = poly


def _create_objects(doc=None,
                    font_file=None,
                    hatch_file=None,
                    hatch_name=None):
    """Create the objects of the test file."""
    if not doc:
        doc = App.activeDocument()
    if not doc:
        doc = App.newDocument()

    # Drafting ##############################################################

    # Line
    _msg(16 * "-")
    _msg("Line")
    Draft.make_line(Vector(0, 0, 0), Vector(500, 500, 0))
    _set_text(["Line"], Vector(0, -200, 0))

    # Wire
    _msg(16 * "-")
    _msg("Wire")
    Draft.make_wire([Vector(1000, 0, 0),
                     Vector(1500, 250, 0),
                     Vector(1500, 500, 0)])
    _set_text(["Wire"], Vector(1000, -200, 0))

    # Fillet
    _msg(16 * "-")
    _msg("Fillet")
    line_1 = Draft.make_line(Vector(2000, 0, 0), Vector(2000, 500, 0))
    line_2 = Draft.make_line(Vector(2000, 500, 0), Vector(2500, 500, 0))
    if App.GuiUp:
        line_1.ViewObject.DrawStyle = "Dotted"
        line_2.ViewObject.DrawStyle = "Dotted"
    doc.recompute()
    Draft.make_fillet([line_1, line_2], 400)
    _set_text(["Fillet"], Vector(2000, -200, 0))

    # Circular arc
    _msg(16 * "-")
    _msg("Circular arc")
    arc = Draft.make_circle(250, startangle=90, endangle=270)
    arc.Placement.Base = Vector(3250, 250, 0)
    _set_text(["Circular arc"], Vector(3000, -200, 0))

    # Circular arc 3 points
    _msg(16 * "-")
    _msg("Circular arc 3 points")
    Draft.make_arc_3points([Vector(4250, 0, 0),
                            Vector(4000, 250, 0),
                            Vector(4250, 500, 0)])
    _set_text(["Circular arc 3 points"], Vector(4000, -200, 0))

    # Circle
    _msg(16 * "-")
    _msg("Circle")
    circle = Draft.make_circle(250)
    circle.Placement.Base = Vector(5250, 250, 0)
    _set_text(["Circle"], Vector(5000, -200, 0))

    # Ellipse
    _msg(16 * "-")
    _msg("Ellipse")
    ellipse = Draft.make_ellipse(250, 150)
    ellipse.Placement.Base = Vector(6250, 150, 0)
    _set_text(["Ellipse"], Vector(6000, -200, 0))

    # Rectangle
    _msg(16 * "-")
    _msg("Rectangle")
    rectangle = Draft.make_rectangle(500, 300, 0)
    rectangle.Placement.Base = Vector(7000, 0, 0)
    _set_text(["Rectangle"], Vector(7000, -200, 0))

    # Polygon
    _msg(16 * "-")
    _msg("Polygon")
    polygon = Draft.make_polygon(5, 250)
    polygon.Placement.Base = Vector(8250, 250, 0)
    _set_text(["Polygon"], Vector(8000, -200, 0))

    # BSpline
    _msg(16 * "-")
    _msg("BSpline")
    Draft.make_bspline([Vector(9000, 0, 0),
                        Vector(9100, 200, 0),
                        Vector(9400, 300, 0),
                        Vector(9500, 500, 0)])
    _set_text(["BSpline"], Vector(9000, -200, 0))

    # Cubic bezier
    _msg(16 * "-")
    _msg("Cubic bezier")
    Draft.make_bezcurve([Vector(10000, 0, 0),
                         Vector(10000, 500, 0),
                         Vector(10500, 0, 0),
                         Vector(10500, 500, 0)], degree=3)
    _set_text(["Cubic bezier"], Vector(10000, -200, 0))

    # N-degree bezier
    _msg(16 * "-")
    _msg("N-degree bezier")
    Draft.make_bezcurve([Vector (11000, 0, 0),
                         Vector (11100, 400, 0),
                         Vector (11250, 250, 0),
                         Vector (11400, 100, 0),
                         Vector (11500, 500, 0)])
    _set_text(["N-degree bezier"], Vector(11000, -200, 0))

    # Point
    _msg(16 * "-")
    _msg("Point")
    point = Draft.make_point(12000, 0, 0)
    if App.GuiUp:
        point.ViewObject.PointSize = 10
    _set_text(["Point"], Vector(12000, -200, 0))

    # Facebinder
    _msg(16 * "-")
    _msg("Facebinder")
    box = doc.addObject("Part::Box", "Box")
    box.Length = 200
    box.Width = 500
    box.Height = 100
    box.Placement.Base = Vector(13000, 0, 0)
    if App.GuiUp:
        box.ViewObject.Visibility = False
    facebinder = Draft.make_facebinder([(box, ("Face1", "Face3", "Face6"))])
    facebinder.Extrusion = 10
    _set_text(["Facebinder"], Vector(13000, -200, 0))

    # Shapestring
    _msg(16 * "-")
    _msg("Shapestring")
    try:
        shape_string = Draft.make_shapestring("Testing",
                                              font_file,
                                              100)
        shape_string.Placement.Base = Vector(14000, 0)
    except Exception:
        _wrn("Shapestring could not be created")
        _wrn("Possible cause: the font file may not exist")
        _wrn(font_file)
    _set_text(["Shapestring"], Vector(14000, -200, 0))

    # Hatch
    _msg(16 * "-")
    _msg("Hatch")
    rectangle = Draft.make_rectangle(500, 300, 0)
    rectangle.Placement.Base = Vector(15000, 0, 0)
    rectangle.MakeFace = True
    if App.GuiUp:
        rectangle.ViewObject.Visibility = False
    doc.recompute()
    try:
        Draft.make_hatch(rectangle,
                         hatch_file,
                         hatch_name,
                         scale=10,
                         rotation=45)
    except Exception:
        _wrn("Hatch could not be created")
        _wrn("Possible cause: the hatch file may not exist")
        _wrn(hatch_file)
    _set_text(["Hatch"], Vector(15000, -200, 0))

    # Annotation ############################################################

    # Text
    _msg(16 * "-")
    _msg("Text")
    text = Draft.make_text(["Testing", "text"], Vector(0, 2100, 0))
    if App.GuiUp:
        text.ViewObject.FontSize = 100
    _set_text(["Text"], Vector(0, 1800, 0))

    # Linear dimension
    _msg(16 * "-")
    _msg("Linear dimension")
    dimension = Draft.make_linear_dimension(Vector(1500, 2000, 0),
                                            Vector(1500, 2400, 0),
                                            Vector(1000, 2200, 0))
    if App.GuiUp:
        dimension.ViewObject.ArrowSize = 15
        dimension.ViewObject.ExtLines = 0
        dimension.ViewObject.ExtOvershoot = 50
        dimension.ViewObject.DimOvershoot = 25
        dimension.ViewObject.FontSize = 50
        dimension.ViewObject.Decimals = 1
        dimension.ViewObject.ShowUnit = False

    line = Draft.make_wire([Vector(1500, 2600, 0),
                            Vector(1500, 3000, 0)])
    doc.recompute()
    dimension = Draft.make_linear_dimension_obj(line, 1, 2,
                                              Vector(1000, 2800, 0))
    if App.GuiUp:
        dimension.ViewObject.ArrowSize = 15
        dimension.ViewObject.ArrowType = "Arrow"
        dimension.ViewObject.ExtLines = -50
        dimension.ViewObject.ExtOvershoot = 50
        dimension.ViewObject.DimOvershoot = 25
        dimension.ViewObject.FontSize = 50
        dimension.ViewObject.Decimals = 1
        dimension.ViewObject.ShowUnit = False

    _set_text(["Dimension"], Vector(1000, 1800, 0))

    # Radius and diameter dimension
    _msg(16 * "-")
    _msg("Radius and diameter dimension")
    circle = Draft.make_circle(200)
    circle.Placement.Base = Vector(2200, 2200, 0)
    circle.MakeFace = False
    doc.recompute()
    dimension = Draft.make_radial_dimension_obj(circle,
                                                1,
                                                "radius",
                                                Vector(2300, 2300, 0))
    if App.GuiUp:
        dimension.ViewObject.ArrowSize = 15
        dimension.ViewObject.FontSize = 50
        dimension.ViewObject.Decimals = 1
        dimension.ViewObject.ShowUnit = False

    circle = Draft.make_circle(200)
    circle.Placement.Base = Vector(2200, 2800, 0)
    circle.MakeFace = False
    doc.recompute()
    dimension = Draft.make_radial_dimension_obj(circle,
                                                1,
                                                "diameter",
                                                Vector(2300, 2900, 0))
    if App.GuiUp:
        dimension.ViewObject.ArrowSize = 15
        dimension.ViewObject.FontSize = 50
        dimension.ViewObject.Decimals = 1
        dimension.ViewObject.ShowUnit = False
    _set_text(["Radius dimension",
               "Diameter dimension"], Vector(2000, 1800, 0))

    # Angular dimension
    _msg(16 * "-")
    _msg("Angular dimension")
    Draft.make_line(Vector(3000, 2000, 0), Vector(3500, 2000, 0))
    Draft.make_line(Vector(3000, 2000, 0), Vector(3500, 2500, 0))
    dimension = Draft.make_angular_dimension(Vector(3000, 2000, 0),
                                             [0, 45],
                                             Vector(3250, 2250, 0))
    if App.GuiUp:
        dimension.ViewObject.ArrowSize = 15
        dimension.ViewObject.FontSize = 50
        dimension.ViewObject.Decimals = 1
    _set_text(["Angle dimension"], Vector(3000, 1800, 0))

    # Label
    _msg(16 * "-")
    _msg("Label")
    place = App.Placement(Vector(4250, 2250, 0), App.Rotation())
    label = Draft.make_label(target_point=Vector(4000, 2000, 0),
                             placement=place,
                             custom_text="Example label",
                             distance=-100)
    label.Text = "Testing"
    if App.GuiUp:
        label.ViewObject.ArrowSize = 15
        label.ViewObject.FontSize = 50
    _set_text(["Label"], Vector(4000, 1800, 0))

    # Array #################################################################

    # Orthogonal array
    _msg(16 * "-")
    _msg("Orthogonal array")
    rectangle = Draft.make_rectangle(100, 50)
    rectangle.Placement.Base = Vector(0, 4000, 0)
    if App.GuiUp:
        rectangle.ViewObject.Visibility = False
    doc.recompute()
    Draft.make_ortho_array(rectangle,
                           Vector(200, 0, 0),
                           Vector(0, 150, 0),
                           Vector(0, 0, 0),
                           3,
                           2,
                           1,
                           use_link=False)
    _set_text(["Orthogonal array"], Vector(0, 3800, 0))

    # Orthogonal link array
    _msg(16 * "-")
    _msg("Orthogonal link array")
    rectangle = Draft.make_rectangle(50, 50)
    rectangle.Placement.Base = Vector(1000, 4000, 0)
    if App.GuiUp:
        rectangle.ViewObject.Visibility = False
    doc.recompute()
    Draft.make_ortho_array(rectangle,
                           Vector(200, 0, 0),
                           Vector(0, 150, 0),
                           Vector(0, 0, 0),
                           3,
                           2,
                           1,
                           use_link=True)
    _set_text(["Orthogonal link array"], Vector(1000, 3800, 0))

    # Polar array
    _msg(16 * "-")
    _msg("Polar array")
    wire = Draft.make_wire([Vector(2000, 4050, 0),
                            Vector(2000, 4000, 0),
                            Vector(2100, 4000, 0)])
    if App.GuiUp:
        wire.ViewObject.Visibility = False
    doc.recompute()
    Draft.make_polar_array(wire,
                           4,
                           90,
                           Vector(2000, 4250, 0),
                           use_link=False)
    _set_text(["Polar array"], Vector(2000, 3800, 0))

    # Polar link array
    _msg(16 * "-")
    _msg("Polar link array")
    wire = Draft.make_wire([Vector(3000, 4050, 0),
                            Vector(3000, 4000, 0),
                            Vector(3050, 4000, 0)])
    if App.GuiUp:
        wire.ViewObject.Visibility = False
    doc.recompute()
    Draft.make_polar_array(wire,
                           4,
                           90,
                           Vector(3000, 4250, 0),
                           use_link=True)
    _set_text(["Polar link array"], Vector(3000, 3800, 0))

    # Circular array
    _msg(16 * "-")
    _msg("Circular array")
    polygon = Draft.make_polygon(5, 30)
    polygon.Placement.Base = Vector(4250, 4250, 0)
    if App.GuiUp:
        polygon.ViewObject.Visibility = False
    doc.recompute()
    Draft.make_circular_array(polygon,
                              110,
                              100,
                              3,
                              1,
                              Vector(0, 0, 1),
                              Vector(0, 0, 0),
                              use_link=False)
    _set_text(["Circular array"], Vector(4000, 3800, 0))

    # Circular link array
    _msg(16 * "-")
    _msg("Circular link array")
    polygon = Draft.make_polygon(6, 30)
    polygon.Placement.Base = Vector(5250, 4250, 0)
    if App.GuiUp:
        polygon.ViewObject.Visibility = False
    doc.recompute()
    Draft.make_circular_array(polygon,
                              110,
                              100,
                              3,
                              1,
                              Vector(0, 0, 1),
                              Vector(0, 0, 0),
                              use_link=True)
    _set_text(["Circular link array"], Vector(5000, 3800, 0))

    # Path array
    _msg(16 * "-")
    _msg("Path array")
    polygon = Draft.make_polygon(3, 30)
    polygon.Placement.Base = Vector(6000, 4000, 0)
    if App.GuiUp:
        polygon.ViewObject.Visibility = False
    spline = Draft.make_bspline([Vector(6000, 4000, 0),
                                 Vector(6100, 4200, 0),
                                 Vector(6400, 4300, 0),
                                 Vector(6500, 4500, 0)])
    doc.recompute()
    Draft.make_path_array(polygon, spline, 5, use_link=False)
    _set_text(["Path array"], Vector(6000, 3800, 0))

    # Path link array
    _msg(16 * "-")
    _msg("Path link array")
    polygon = Draft.make_polygon(4, 30)
    polygon.Placement.Base = Vector(7000, 4000, 0)
    if App.GuiUp:
        polygon.ViewObject.Visibility = False
    spline = Draft.make_bspline([Vector(7000, 4000, 0),
                                 Vector(7100, 4200, 0),
                                 Vector(7400, 4300, 0),
                                 Vector(7500, 4500, 0)])
    doc.recompute()
    Draft.make_path_array(polygon, spline, 5, use_link=True)
    _set_text(["Path link array"], Vector(7000, 3800, 0))

    # Point array
    _msg(16 * "-")
    _msg("Point array")
    polygon = Draft.make_polygon(3, 30)
    polygon.Placement.Base = Vector(8000, 4000, 0)
    point_1 = Draft.make_point(8030, 4030, 0)
    point_2 = Draft.make_point(8030, 4250, 0)
    point_3 = Draft.make_point(8470, 4250, 0)
    point_4 = Draft.make_point(8470, 4470, 0)
    add_list, delete_list = Draft.upgrade([point_1, point_2,
                                           point_3, point_4])
    compound = add_list[0]
    if App.GuiUp:
        compound.ViewObject.PointSize = 5
    doc.recompute()
    Draft.make_point_array(polygon, compound, use_link=False)
    _set_text(["Point array"], Vector(8000, 3800, 0))

    # Point link array
    _msg(16 * "-")
    _msg("Point link array")
    polygon = Draft.make_polygon(4, 30)
    polygon.Placement.Base = Vector(9000, 4000, 0)
    point_1 = Draft.make_point(9030, 4030, 0)
    point_2 = Draft.make_point(9030, 4250, 0)
    point_3 = Draft.make_point(9470, 4250, 0)
    point_4 = Draft.make_point(9470, 4470, 0)
    add_list, delete_list = Draft.upgrade([point_1, point_2,
                                           point_3, point_4])
    compound = add_list[0]
    if App.GuiUp:
        compound.ViewObject.PointSize = 5
    doc.recompute()
    Draft.make_point_array(polygon, compound, use_link=True)
    _set_text(["Point link array"], Vector(9000, 3800, 0))

    # Miscellaneous #########################################################

    # Mirror
    _msg(16 * "-")
    _msg("Mirror")
    wire = Draft.make_wire([Vector(0, 6000, 0),
                            Vector(150, 6200, 0),
                            Vector(500, 6000, 0)])
    Draft.mirror(wire,
                 Vector(0, 6250, 0),
                 Vector(500, 6250, 0))
    _set_text(["Mirror"], Vector(0, 5800, 0))

    # Clone
    _msg(16 * "-")
    _msg("Clone")
    wire = Draft.make_wire([Vector(1000, 6000, 0),
                            Vector(1150, 6200, 0),
                            Vector(1500, 6000, 0)])
    Draft.make_clone(wire, Vector(0, 300, 0))
    _set_text(["Clone"], Vector(1000, 5800, 0))

    # Shape2DView
    _msg(16 * "-")
    _msg("Shape2DView")
    place = App.Placement(Vector(2000, 6000, 0),
                          App.Rotation(Vector(0, 0, 1), Vector(1, 2, 3)))
    box = doc.addObject("Part::Box", "Box")
    box.Length = 200
    box.Width = 500
    box.Height = 100
    box.Placement = place
    if App.GuiUp:
        box.ViewObject.Visibility = False
    Draft.make_shape2dview(box)
    _set_text(["Shape2DView"], Vector(2000, 5800, 0))

    # WorkingPlaneProxy
    _msg(16 * "-")
    _msg("WorkingPlaneProxy")
    place = App.Placement(Vector(3250, 6250, 0), App.Rotation())
    proxy = Draft.make_workingplaneproxy(place)
    if App.GuiUp:
        proxy.ViewObject.DisplaySize = 500
        proxy.ViewObject.ArrowSize = 50
    _set_text(["WorkingPlaneProxy"], Vector(3000, 5800, 0))

    # Layer
    _msg(16 * "-")
    _msg("Layer")
    layer = Draft.make_layer("Custom layer",
                             line_color=(0.33, 0.0, 0.49),
                             shape_color=(0.56, 0.89, 0.56),
                             line_width=4,
                             draw_style="Solid",
                             transparency=50)
    box = doc.addObject("Part::Box", "Box")
    box.Length = 200
    box.Width = 500
    box.Height = 100
    box.Placement.Base = Vector(4000, 6000, 0)
    sphere = doc.addObject("Part::Sphere", "Sphere")
    sphere.Radius = 100
    sphere.Placement.Base = Vector(4400, 6250, 0)
    layer.Proxy.addObject(layer, box)
    layer.Proxy.addObject(layer, sphere)
    _set_text(["Layer"], Vector(4000, 5800, 0))

    doc.recompute()


def create_test_file(font_file=App.getResourceDir()+"Mod/TechDraw/Resources/fonts/osifont-lgpl3fe.ttf",
                     hatch_file=App.getResourceDir()+"Mod/TechDraw/PAT/FCPAT.pat",
                     hatch_name="Horizontal5"):
    """Create a complete test file of Draft objects.

    It draws a frame with information on the software used to create
    the test document, and fills it with every object that can be created.

    Parameters
    ----------
    font_file: str, optional
        It defaults to `App.getHomePath()+"data/Mod/TechDraw/Resources/fonts/osifont-lgpl3fe.ttf"`
        It is the full path of a font file to be used to create a `Draft ShapeString`.
        If the file is not found, this object is not created.

    hatch_file: str, optional
        It defaults to `App.getHomePath()+"data/Mod/TechDraw/PAT/FCPAT.pat"`
        It is the full path of a PAT file to be used to create a `Draft Hatch`.
        If the file is not found, this object is not created.

    hatch_name: str, optional
        It defaults to `"Horizontal5"`
        It is the name of a hatch pattern in the hatch_file.

    Returns
    -------
    App::Document
        A reference to the test document that was created.
    """
    _msg(16 * "-")
    _msg("If the units tests fail, this script may fail as well")
    doc = App.newDocument()

    _create_frame(doc=doc)
    _create_objects(doc=doc, font_file=font_file, hatch_file=hatch_file, hatch_name=hatch_name)

    if App.GuiUp:
        Gui.runCommand("Std_ViewFitAll")
        Gui.Selection.clearSelection()

    return doc

## @}


if __name__ == "__main__":
    create_test_file()
