# ***************************************************************************
# *   Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net>        *
# *   Copyright (c) 2009, 2010 Ken Cline <cline@frii.com>                   *
# *   Copyright (c) 2020 FreeCAD Developers                                 *
# *                                                                         *
# *   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.                                 *
# *                                                                         *
# *   This program 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 this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************
"""Provides the object code for the Shape2dView object."""
## @package shape2dview
# \ingroup draftobjects
# \brief Provides the object code for the Shape2dView object.

## \addtogroup draftobjects
# @{
from PySide.QtCore import QT_TRANSLATE_NOOP

import FreeCAD as App
import DraftVecUtils
import draftutils.utils as utils
import draftutils.gui_utils as gui_utils
import draftutils.groups as groups

from draftutils.translate import translate
from draftobjects.base import DraftObject


class Shape2DView(DraftObject):
    """The Shape2DView object"""

    def __init__(self,obj):

        self.setProperties(obj)
        super(Shape2DView, self).__init__(obj, "Shape2DView")

    def setProperties(self,obj):

        pl = obj.PropertiesList

        if not "Base" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "The base object this 2D view must represent")
            obj.addProperty("App::PropertyLink", "Base",
                            "Draft", _tip)
        if not "Projection" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "The projection vector of this object")
            obj.addProperty("App::PropertyVector", "Projection",
                            "Draft", _tip)
            obj.Projection = App.Vector(0,0,1)
        if not "ProjectionMode" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "The way the viewed object must be projected")
            obj.addProperty("App::PropertyEnumeration", "ProjectionMode",
                            "Draft", _tip)
            obj.ProjectionMode = ["Solid", "Individual Faces",
                                  "Cutlines", "Cutfaces","Solid faces"]
        if not "FaceNumbers" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "The indices of the faces to be projected in Individual Faces mode")
            obj.addProperty("App::PropertyIntegerList", "FaceNumbers",
                            "Draft", _tip)
        if not "HiddenLines" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "Show hidden lines")
            obj.addProperty("App::PropertyBool", "HiddenLines",
                            "Draft", _tip)
            obj.HiddenLines = False
        if not "FuseArch" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "Fuse wall and structure objects of same type and material")
            obj.addProperty("App::PropertyBool", "FuseArch",
                            "Draft", _tip)
        if not "Tessellation" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "Tessellate Ellipses and B-splines into line segments")
            obj.addProperty("App::PropertyBool", "Tessellation",
                            "Draft", _tip)
            obj.Tessellation = False
        if not "InPlace" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "For Cutlines and Cutfaces modes, this leaves the faces at the cut location")
            obj.addProperty("App::PropertyBool", "InPlace",
                            "Draft", _tip)
            obj.InPlace = True
        if not "SegmentLength" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "Length of line segments if tessellating Ellipses or B-splines into line segments")
            obj.addProperty("App::PropertyFloat", "SegmentLength",
                            "Draft", _tip)
            obj.SegmentLength = .05
        if not "VisibleOnly" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "If this is True, this object will include only visible objects")
            obj.addProperty("App::PropertyBool", "VisibleOnly",
                            "Draft", _tip)
            obj.VisibleOnly = False
        if not "ExclusionPoints" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "A list of exclusion points. Any edge touching any of those points will not be drawn.")
            obj.addProperty("App::PropertyVectorList", "ExclusionPoints",
                            "Draft", _tip)
        if not "ExclusionNames" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "A list of exclusion object names. Any object viewed that matches a name from the list will not be drawn.")
            obj.addProperty("App::PropertyStringList", "ExclusionNames",
                            "Draft", _tip)
        if not "OnlySolids" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "If this is True, only solid geometry is handled. This overrides the base object's Only Solids property")
            obj.addProperty("App::PropertyBool", "OnlySolids",
                            "Draft", _tip)
        if not "Clip" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "If this is True, the contents are clipped to the borders of the section plane, if applicable. This overrides the base object's Clip property")
            obj.addProperty("App::PropertyBool", "Clip",
                            "Draft", _tip)
        if not "AutoUpdate" in pl:
            _tip = QT_TRANSLATE_NOOP("App::Property",
                    "This object will be recomputed only if this is True.")
            obj.addProperty("App::PropertyBool", "AutoUpdate",
                            "Draft", _tip)
            obj.AutoUpdate = True

    def onDocumentRestored(self, obj):

        self.setProperties(obj)

    def getProjected(self,obj,shape,direction):

        "returns projected edges from a shape and a direction"
        import Part
        import TechDraw
        import DraftGeomUtils
        edges = []
        _groups = TechDraw.projectEx(shape, direction)
        for g in _groups[0:5]:
            if not g.isNull():
                edges.append(g)
        if getattr(obj, "HiddenLines", False):
            for g in _groups[5:]:
                if not g.isNull():
                    edges.append(g)
        edges = self.cleanExcluded(obj,edges)
        if getattr(obj, "Tessellation", False):
            return DraftGeomUtils.cleanProjection(Part.makeCompound(edges),
                                                  obj.Tessellation,
                                                  obj.SegmentLength)
        else:
            return Part.makeCompound(edges)

    def cleanExcluded(self,obj,shapes):

        """removes any edge touching exclusion points"""
        import Part
        MAXDIST = 0.0001
        if (not hasattr(obj,"ExclusionPoints")) or (not obj.ExclusionPoints):
            return shapes
        #verts = [Part.Vertex(obj.Placement.multVec(p)) for p in obj.ExclusionPoints]
        verts = [Part.Vertex(p) for p in obj.ExclusionPoints]
        nedges = []
        for s in shapes:
            for e in s.Edges:
                for v in verts:
                    try:
                        d = e.distToShape(v)
                        if d and (d[0] <= MAXDIST):
                            break
                    except RuntimeError:
                        print("FIXME: shape2dview: distance unavailable for edge",e,"in",obj.Label)
                else:
                    nedges.append(e)
        return nedges

    def excludeNames(self,obj,objs):
        if hasattr(obj,"ExclusionNames"):
            objs = [o for o in objs if not(o.Name in obj.ExclusionNames)]
            return objs

    def _get_shapes(self, shape, onlysolids=False):
        if onlysolids:
            return shape.Solids
        if shape.isNull():
            return []
        if shape.ShapeType == "Compound":
            return shape.SubShapes
        return [shape.copy()]

    def execute(self, obj):
        if self.props_changed_placement_only(obj) \
                or not getattr(obj, "AutoUpdate", True):
            obj.positionBySupport()
            self.props_changed_clear()
            return

        import Part
        import DraftGeomUtils
        pl = obj.Placement
        if obj.Base:
            if utils.get_type(obj.Base) in ["BuildingPart","SectionPlane"]:
                objs = []
                if utils.get_type(obj.Base) == "SectionPlane":
                    objs = self.excludeNames(obj,obj.Base.Objects)
                    cutplane = obj.Base.Shape
                else:
                    objs = self.excludeNames(obj,obj.Base.Group)
                    cutplane = Part.makePlane(1000, 1000, App.Vector(-500, -500, 0))
                    m = 1
                    if obj.Base.ViewObject and hasattr(obj.Base.ViewObject,"CutMargin"):
                        m = obj.Base.ViewObject.CutMargin.Value
                    cutplane.translate(App.Vector(0,0,m))
                    cutplane.Placement = cutplane.Placement.multiply(obj.Base.Placement)
                if objs:
                    onlysolids = True
                    if hasattr(obj.Base,"OnlySolids"):
                        onlysolids = obj.Base.OnlySolids
                    if hasattr(obj,"OnlySolids"): # override base object
                        onlysolids = obj.OnlySolids
                    import Arch
                    objs = groups.get_group_contents(objs, walls=True)
                    if getattr(obj,"VisibleOnly",True):
                        objs = gui_utils.remove_hidden(objs)
                    shapes = []
                    if getattr(obj,"FuseArch", False):
                        shtypes = {}
                        for o in objs:
                            if utils.get_type(o) in ["Wall","Structure"]:
                                shtypes.setdefault(
                                    o.Material.Name
                                    if (hasattr(o,"Material") and o.Material) else "None",
                                    []
                                ).extend(self._get_shapes(o.Shape, onlysolids))
                            elif hasattr(o, "Shape"):
                                shapes.extend(self._get_shapes(o.Shape, onlysolids))
                        for k, v in shtypes.items():
                            v1 = v.pop()
                            if v:
                                try:
                                    v1 = v1.multiFuse(v)
                                except (RuntimeError, Part.OCCError):
                                    # multifuse can fail
                                    for v2 in v:
                                        v1 = v1.fuse(v2)
                                try:
                                    v1 = v1.removeSplitter()
                                except (RuntimeError, Part.OCCError):
                                    pass
                            if v1.Solids:
                                shapes.extend(v1.Solids)
                            else:
                                print("Shape2DView: Fusing Arch objects produced non-solid results")
                                shapes.extend(v1.SubShapes)
                    else:
                        for o in objs:
                            if hasattr(o, "Shape"):
                                shapes.extend(self._get_shapes(o.Shape, onlysolids))
                    clip = False
                    if hasattr(obj.Base,"Clip"):
                        clip = obj.Base.Clip
                    if hasattr(obj,"Clip"): #override base object
                        clip = obj.Clip
                    depth = None
                    if hasattr(obj.Base,"Depth"):
                        depth = obj.Base.Depth.Value
                    cutp, cutv, iv = Arch.getCutVolume(cutplane, shapes, clip, depth)
                    cuts = []
                    opl = App.Placement(obj.Base.Placement)
                    proj = opl.Rotation.multVec(App.Vector(0, 0, 1))
                    if obj.ProjectionMode in ["Solid","Solid faces"]:
                        shapes_to_cut = shapes
                        if obj.ProjectionMode == "Solid faces":
                            shapes_to_cut = []
                            for s in shapes:
                                shapes_to_cut.extend(s.Faces)
                        for sh in shapes_to_cut:
                            if cutv:
                                if sh.Volume < 0:
                                    sh.reverse()
                                #if cutv.BoundBox.intersect(sh.BoundBox):
                                #    c = sh.cut(cutv)
                                #else:
                                #    c = sh.copy()
                                c = sh.cut(cutv)
                                cuts.extend(self._get_shapes(c, onlysolids))
                            else:
                                cuts.extend(self._get_shapes(sh, onlysolids))
                        comp = Part.makeCompound(cuts)
                        obj.Shape = self.getProjected(obj,comp,proj)
                    elif obj.ProjectionMode in ["Cutlines", "Cutfaces"]:
                        for sh in shapes:
                            if sh.Volume < 0:
                                sh.reverse()
                            c = sh.section(cutp)
                            if hasattr(obj,"InPlace"):
                                if not obj.InPlace:
                                    c = self.getProjected(obj, c, proj)
                            faces = []
                            if (obj.ProjectionMode == "Cutfaces") and (sh.ShapeType == "Solid"):
                                wires = DraftGeomUtils.findWires(c.Edges)
                                for w in wires:
                                    if w.isClosed():
                                        faces.append(Part.Face(w))
                            if faces:
                                cuts.extend(faces)
                            else:
                                cuts.append(c)
                        comp = Part.makeCompound(cuts)
                        opl = App.Placement(obj.Base.Placement)
                        comp.Placement = opl.inverse()
                        if comp:
                            obj.Shape = comp

            elif obj.Base.isDerivedFrom("App::DocumentObjectGroup"):
                shapes = []
                objs = self.excludeNames(obj,groups.get_group_contents(obj.Base))
                for o in objs:
                    if hasattr(o, "Shape"):
                        shapes.extend(self._get_shapes(o.Shape))
                if shapes:
                    import Part
                    comp = Part.makeCompound(shapes)
                    obj.Shape = self.getProjected(obj,comp,obj.Projection)

            elif hasattr(obj.Base, "Shape"):
                if not DraftVecUtils.isNull(obj.Projection):
                    if obj.ProjectionMode == "Solid":
                        obj.Shape = self.getProjected(obj,obj.Base.Shape,obj.Projection)
                    elif obj.ProjectionMode == "Individual Faces":
                        import Part
                        if obj.FaceNumbers:
                            faces = []
                            for i in obj.FaceNumbers:
                                if len(obj.Base.Shape.Faces) > i:
                                    faces.append(obj.Base.Shape.Faces[i])
                            views = []
                            for f in faces:
                                views.append(self.getProjected(obj,f,obj.Projection))
                            if views:
                                obj.Shape = Part.makeCompound(views)
                    else:
                        App.Console.PrintWarning(obj.ProjectionMode+" mode not implemented\n")

        obj.Placement = pl
        obj.positionBySupport()
        self.props_changed_clear()

    def onChanged(self, obj, prop):
        self.props_changed_store(prop)


# Alias for compatibility with v0.18 and earlier
_Shape2DView = Shape2DView

## @}
