# /**********************************************************************
# *                                                                     *
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com>   *
# *                                                                     *
# * 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                                                                 *
# *                                                                     *
# ***********************************************************************

import os

import ArchComponent
import FreeCAD
import Part

if FreeCAD.GuiUp:
    import FreeCADGui
    from PySide.QtCore import QT_TRANSLATE_NOOP
    from pivy import coin
    import draftguitools.gui_trackers as DraftTrackers
else:
    # \cond
    def translate(ctxt, txt):
        return txt


    def QT_TRANSLATE_NOOP(ctxt, txt):
        return txt
    # \endcond

__title__ = "PVPlant Trench"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"

import PVPlantResources
from PVPlantResources import DirIcons as DirIcons

TrenchFillTypes = ["Arena", "Tierra de excavación", "Cemento", "Graba"]
# sand color: ece2c6 / 236 226 198
# tierra color: 4e3b31 / 78 59 49
MateriralColor = [(0.9255, 0.8863, 0.7765), (0.3059, 0.2314, 0.1922), (0.4902, 0.5176, 0.4431)]


def makeTrenchNode(point=None, trench=None):
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "TrenchNode")
    TrenchNode(obj)
    ViewProviderTrenchNode(obj.ViewObject)
    if point:
        obj.Placement.Base = point
    if trench:
        tl = [trench]
        obj.TrenchList = tl
    return obj


class TrenchNode:
    def __init__(self, obj):
        ''' Initialize the class. '''
        self.Type = None
        self.obj = None
        self.setProperties(obj)

    def setProperties(self, obj):
        pl = obj.PropertiesList

        if not ("TrenchList" in pl):
            obj.addProperty("App::PropertyLinkList",
                            "TrenchList",
                            "TrenchNode",
                            "Connection")

        self.obj = obj
        self.Type = "TrenchNode"
        obj.Proxy = self

    def onDocumentRestored(self, obj):
        """ Method run when the document is restored. """
        self.setProperties(obj)

    def execute(self, obj):
        pl = obj.Placement
        obj.Shape = Part.makeSphere(1500)
        obj.Placement = pl


class ViewProviderTrenchNode:
    def __init__(self, vobj):
        ''' Set view properties. '''
        vobj.Proxy = self

    def getIcon(self):
        ''' Return object treeview icon. '''
        return str(os.path.join(DirIcons, "node.jpg"))


def makeTrench(base=None):
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Trench")
    Trench(obj)
    ViewProviderTrench(obj.ViewObject)
    obj.Base = base

    try:
        folder = FreeCAD.ActiveDocument.Trenches
    except:
        folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Trenches')
        folder.Label = "Trenches"
    folder.addObject(obj)
    return obj


class Trench(ArchComponent.Component):
    def __init__(self, obj):
        # Definición de Variables:
        ArchComponent.Component.__init__(self, obj)
        self.numLayersSegment = dict()
        self.setProperties(obj)
        self.number_old_segments = int(obj.Segments)
        self.obj = obj
        self.route = False

    def setProperties(self, obj):
        # Definicion de Propiedades:
        pl = obj.PropertiesList

        if not ("OffsetStart" in pl):
            obj.addProperty("App::PropertyLength",
                            "OffsetStart",
                            "Trench",
                            "Offset al comienzo")

        if not ("OffsetEnd" in pl):
            obj.addProperty("App::PropertyLength",
                            "OffsetEnd",
                            "Trench",
                            "Offset al final")

        if not ("Width" in pl):
            obj.addProperty("App::PropertyLength",
                            "Width",
                            "Trench",
                            QT_TRANSLATE_NOOP("App::Property", "Connection")).Width = 600

        if not ("Trapezoid" in pl):
            obj.addProperty("App::PropertyBool",
                            "Trapezoid",
                            "Trench",
                            "Connection").Trapezoid = False

        if not ("Segments" in pl):
            obj.addProperty("App::PropertyIntegerConstraint",
                            "Segments",
                            "Segments",
                            "Segments").Segments = (1, 1, 100, 1)

        # Cables: ------------------------
        if not ("Cables" in pl):
            obj.addProperty("App::PropertyIntegerConstraint",
                            "Cables",
                            "Cables",
                            "Número de cables").Cables = (1, 1, 20, 1)

        if not ("DistanceToExcavationBotton" in pl):
            obj.addProperty("App::PropertyLength",
                            "DistanceToExcavationBotton",
                            "Cables",
                            "Separación del cable al fondo de excavación").DistanceToExcavationBotton = 100

        # Outputs: ------------------------
        if not ("Length" in pl):
            obj.addProperty("App::PropertyLength",
                            "Length",
                            "Outputs",
                            "Length")
        obj.setEditorMode("Length", 1)

        if not ("Volume" in pl):
            obj.addProperty("App::PropertyVolume",
                            "Volume",
                            "Outputs",
                            "Volume")
        obj.setEditorMode("Volume", 1)

        if not ("Type" in pl):
            obj.addProperty("App::PropertyString",
                            "Type",
                            "Base",
                            "Type").Type = "Trench"
        obj.setEditorMode("Type", 1)

        self.Type = obj.Type
        obj.Proxy = self
        obj.IfcType = "Civil Element"  ## puede ser: Cable Carrier Segment
        #obj.setEditorMode("IfcType", 1)

    def onDocumentRestored(self, obj):
        ArchComponent.Component.onDocumentRestored(self, obj)
        self.setProperties(obj)

    def onBeforeChange(self, obj, prop):
        if prop == "Segments":
            self.number_old_segments = int(obj.Segments)

    def createSegment(self, obj, ind):
        pl = obj.PropertiesList
        name = f"Start{ind}"
        if not (name in pl):
            obj.addProperty("App::PropertyLength",
                            name,
                            f"Segment{ind}",
                            "Number of layers")
            if ind == 1:
                obj.setEditorMode(name, 1)
                val = 0
            else:
                val = obj.getPropertyByName(f"Start{ind - 1}").Value + 100
            setattr(obj, name, val)

        name = f"LayersSeg{ind}"
        if not (name in pl):
            obj.addProperty("App::PropertyIntegerConstraint",
                            name,
                            f"Segment{ind}",
                            "Number of layers")
            setattr(obj, name, (2, 2, 6, 1))

        name = f"Deep{ind}"
        if not (name in pl):
            obj.addProperty("App::PropertyLength",
                            name,
                            f"Segment{ind}",
                            "Number of layers")
            setattr(obj, name, 900)

    def removeSegment(self, obj, ind):
        lay = getattr(obj, f"LayersSeg{ind}")       # TODO: cambiar a S(xx)Layers
        for i in range(1, lay + 1):
            obj.removeProperty(f"Layer{i}HeigthSeg{ind}")
            obj.removeProperty(f"Layer{i}MaterialSeg{ind}")
        obj.removeProperty(f"LayersSeg{ind}")
        obj.removeProperty(f"Start{ind}")
        obj.removeProperty(f"Deep{ind}")

    def createLayer(self, obj, seg_num, ind):
        pl = obj.PropertiesList
        name = f"Layer{ind + 1}HeigthSeg{seg_num}"  # TODO: cambiar a S(xx)Layer(yy)Heigth
        if not (name in pl):
            obj.addProperty("App::PropertyLength",
                            name,
                            f"Segment{seg_num}",
                            "Number of layers")
            setattr(obj, name, 450)

        name = f"Layer{ind + 1}MaterialSeg{seg_num}"  # TODO: cambiar a S(xx)Layer(yy)Material
        if not (name in pl):
            obj.addProperty("App::PropertyEnumeration",
                            name,
                            f"Segment{seg_num}",
                            "Number of layers")
            setattr(obj, name, TrenchFillTypes)
            setattr(obj, name, TrenchFillTypes[ind])

    def removeLayer(self, obj, seg_num, ind):
        obj.removeProperty(f"Layer{ind}HeigthSeg{seg_num}")
        obj.removeProperty(f"Layer{ind}MaterialSeg{seg_num}")

    def onChanged(self, obj, prop):
        if prop == "Segments":
            if self.number_old_segments == int(obj.Segments):
                pass
            elif self.number_old_segments < int(obj.Segments):
                for i in range(self.number_old_segments + 1, int(obj.Segments) + 1):
                    self.createSegment(obj, i)
            else:
                for i in range(self.number_old_segments, int(obj.Segments), -1):
                    self.removeSegment(obj, i)

        if prop.startswith("LayersSeg"):
            seg_num = int(prop[len("LayersSeg"):])
            if not (prop in self.numLayersSegment):
                for i in range(obj.getPropertyByName(prop)):
                    self.createLayer(obj, seg_num, i)
            elif self.numLayersSegment[prop] < obj.getPropertyByName(prop):
                for i in range(self.numLayersSegment[prop], obj.getPropertyByName(prop)):
                    self.createLayer(obj, seg_num, i)
            elif self.numLayersSegment[prop] > obj.getPropertyByName(prop):
                for i in range(self.numLayersSegment[prop], obj.getPropertyByName(prop), -1):
                    self.removeLayer(obj, seg_num, i)
            self.numLayersSegment[prop] = int(obj.getPropertyByName(prop))

    def calculateOffset(self, obj, dist):
        offset = obj.makeOffset2D(dist, 2, False, True, True)
        pts_o = []
        for offseti, offsetv in enumerate(offset.Vertexes):
            lns = list()
            for point in pts:
                tmp = FreeCAD.Vector(point)
                tmp.z = 0
                lns.append(offsetv.Point.sub(tmp).Length)

            tmp = FreeCAD.Vector(offset.Vertexes[offseti].Point)
            tmp.z = pts[lns.index(min(lns))].z
            pts_o.append(tmp)

        if dist < 0:
            pts_o.insert(0, pts_o.pop(1))
            pts_o.reverse()

        return pts_o #Part.Wire(Part.makePolygon(points))

    def execute(self, obj):
        # obj.Shape: compound
        # |- Segments: compound
        # |-- segment x: compound
        # |---- Layer x

        # |- Path for cables: compound
        # |-- Path 1
        # |-- Path x

        import Part
        import MeshPart as mp

        def getVector(edge):
            p1 = edge.Vertexes[0].Point
            p1.z = 0
            p2 = edge.Vertexes[1].Point
            p2.z = 0
            return p2.sub(p1)

        def getsegments(wire): #deepseek
            """Divide un wire en segmentos rectos basados en cambios de dirección (sin splitWiresByCurvature)"""
            import Part
            from math import degrees

            segments = []
            current_segment = []
            angle_threshold = 1.0  # Grados para considerar cambio de dirección

            def get_angle(v1, v2):
                return degrees(v1.getAngle(v2))

            edges = wire.Edges
            for i in range(len(edges)):
                if i == 0:
                    current_segment.append(edges[i])
                    continue

                prev_edge = edges[i - 1]
                curr_edge = edges[i]

                # Vectores de dirección
                v1 = prev_edge.tangentAt(prev_edge.FirstParameter)
                v2 = curr_edge.tangentAt(curr_edge.FirstParameter)

                angle = get_angle(v1, v2)
                if angle > angle_threshold:
                    segments.append(Part.Wire(current_segment))
                    current_segment = [curr_edge]
                else:
                    current_segment.append(curr_edge)

            if current_segment:
                segments.append(Part.Wire(current_segment))

            return segments

        def getsegments_old(wire):
            import math
            segments = []

            segment = [wire.Edges[0]]
            for i in range(1, len(wire.Edges)):
                vec1 = getVector(wire.Edges[i - 1])
                vec2 = getVector(wire.Edges[i])
                angle = math.degrees(vec1.getAngle(vec2))
                if angle <= 1:
                    segment.append(wire.Edges[i])
                else:
                    segments.append(Part.Wire(segment))
                    segment = [wire.Edges[i]]
            segments.append(Part.Wire(segment))
            return segments

        w = self.calculatePathWire(obj)
        if w is None:
            return
        obj.Base.Visibility = False
        segmetPaths = self.getSegmentPaths(obj, w)
        land = FreeCAD.ActiveDocument.Site.Terrain.Mesh
        d = obj.Width.Value / 2
        sh = Part.makeCompound([])

        for i in range(1, obj.Segments + 1):
            w = segmetPaths[i - 1]
            tmp = mp.projectShapeOnMesh(w, land, FreeCAD.Vector(0, 0, 1))
            pts = []
            for p in tmp:
                if len(p) > 0:
                    pts.extend(p)

            pts_plane = list()
            for pt in pts:
                tmp = FreeCAD.Vector(pt)
                tmp.z = 0
                pts_plane.append(tmp)
            path_plane = Part.makePolygon(pts_plane)

            points = self.calculateOffset(path_plane, d)
            points2 = self.calculateOffset(path_plane, -d)

            w1 = Part.Wire(Part.makePolygon(points))
            w2 = Part.Wire(Part.makePolygon(points2))

            segments1 = getsegments(w1)
            segments2 = getsegments(w2)
            lines = []
            for sidx in range(len(segments1)):
                reverse = False
                if len(segments1[sidx].Vertexes) >= len(segments2[sidx].Vertexes):
                    pl1 = segments1[sidx]
                    pl2 = segments2[sidx]
                else:
                    pl1 = segments2[sidx]
                    pl2 = segments1[sidx]
                    reverse = True

                lines.append(Part.LineSegment(pl1.Vertexes[0].Point, pl2.Vertexes[0].Point).toShape())
                for i1 in range(1, len(pl1.Vertexes) - 1):
                    p1 = pl1.Vertexes[i1].Point
                    tmp = []
                    for i2 in range(1, len(pl2.Vertexes)):
                        p2 = pl2.Vertexes[i2].Point
                        tmp.append([p2, p1.sub(p2).Length])
                    p2 = min(tmp, key=lambda x: x[1])[0]
                    if not reverse:
                        lines.append(Part.LineSegment(p1, p2).toShape())
                    else:
                        lines.append(Part.LineSegment(p2, p1).toShape())

            lines.append(Part.LineSegment(segments1[-1].Vertexes[-1].Point, segments2[-1].Vertexes[-1].Point).toShape())
            loft = Part.makeLoft(lines, False, True, False)

            lay = Part.makeCompound([])
            if not obj.Trapezoid:
                zz = -obj.getPropertyByName(f"Deep{i}").Value
                for j in range(1, obj.getPropertyByName(f"LayersSeg{i}") + 1):
                    h = obj.getPropertyByName(f"Layer{j}HeigthSeg{i}").Value
                    tmp = loft.extrude(FreeCAD.Vector(0,0, h))
                    tmp.Placement.Base.z = zz
                    lay.add(tmp)
                    zz += h
            else:
                ''' to be defined...'''
            sh.add(lay)

        gap = 250
        if obj.Cables % 2 == 0:
            xx = -gap/2 - gap * (obj.Cables / 2 - 1) # 25 se cambiará a una variable que indicará la separación entre cables
        else:
            xx = - gap * int(obj.Cables / 2)

        paths = Part.makeCompound([])
        '''for i in range(obj.Cables):
            o1 = path_plane.makeOffset2D(xx, 2, False, True, True)
            points = calculateOffset(o1)
            for point in points:
                point.z = point.z - obj.Height.Value + obj.DistanceToExcavationBotton.Value
            if points[2].sub(points[0]).Length < points[2].sub(points[1]).Length:
                points.insert(0, points.pop(1))
            if xx < 0:
                points.reverse()
            p = Part.makePolygon(points)
            paths.add(p)
            xx += gap'''

        obj.Shape = Part.makeCompound([sh, paths])
        obj.Length = path_plane.Length
        obj.Volume = sh.Volume

        colors = []
        for i in range(1, obj.Segments + 1):
            for j in range(1, obj.getPropertyByName(f"LayersSeg{i}") + 1):
                material = obj.getPropertyByName(f"Layer{j}MaterialSeg{i}")
                #material = obj.getPropertyByName("Layer{0}Material".format(i + 1))
                color = MateriralColor[TrenchFillTypes.index(material)]
                colors.extend([color] * len(obj.Shape.SubShapes[0].SubShapes[i - 1].SubShapes[j - 1].Faces))
        obj.ViewObject.DiffuseColor = colors

    def calculatePathWire(self, obj):
        if obj.Base:
            wire = None
            if hasattr(obj.Base.Shape, 'Wires') and obj.Base.Shape.Wires:
                wire = obj.Base.Shape.Wires[0]
            elif obj.Base.Shape.Edges:
                wire = Part.Wire(obj.Base.Shape.Edges)

            if obj.OffsetStart.Value:
                d = obj.OffsetStart.Value
                for i, e in enumerate(wire.Edges):
                    if e.Length < d:
                        d -= e.Length
                    else:
                        v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
                        v.multiply(d)
                        p = e.Vertexes[0].Point.add(v)
                        pts = [ver.Point for ver in wire.Vertexes]
                        pts = [p] + pts[i + 1:]
                        wire = Part.makePolygon(pts)
                        break

            if obj.OffsetEnd.Value:
                d = wire.Length - obj.OffsetEnd.Value
                for i, e in enumerate(wire.Edges):
                    if e.Length < d:
                        d -= e.Length
                    else:
                        v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
                        v.multiply(d)
                        p = e.Vertexes[0].Point.add(v)
                        pts = [ver.Point for ver in wire.Vertexes]
                        pts = pts[:i + 1] + [p]
                        wire = Part.makePolygon(pts)
                        break
            return wire
        return None

    def getSegmentPaths(self, obj, wire):
        segmetPaths = []
        for i in range(1, obj.Segments + 1):
            s = obj.getPropertyByName(f"Start{i}").Value
            if (i + 1) <= obj.Segments:
                ln = obj.getPropertyByName(f"Start{i + 1}").Value
            else:
                ln = wire.Length
            f = ln - s
            segmetPaths.append(self.calcualteSementPath(wire, s, f))
        return segmetPaths

    def calcualteSementPath(self, w, start, length):
        for i, e in enumerate(w.Edges):
            if e.Length < start:
                start -= e.Length
            else:
                v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
                v.multiply(start)
                p = e.Vertexes[0].Point.add(v)
                pts = [ver.Point for ver in w.Vertexes]
                pts = [p] + pts[i + 1:]
                w = Part.makePolygon(pts)
                break

        for i, e in enumerate(w.Edges):
            if e.Length < length:
                length -= e.Length
            else:
                v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
                v.multiply(length)
                p = e.Vertexes[0].Point.add(v)
                pts = [ver.Point for ver in w.Vertexes]
                pts = pts[:i + 1] + [p]
                w = Part.makePolygon(pts)
                break
        return w


class ViewProviderTrench(ArchComponent.ViewProviderComponent):
    def __init__(self, vobj):
        ArchComponent.ViewProviderComponent.__init__(self, vobj)

    def getIcon(self):
        return str(os.path.join(PVPlantResources.DirIcons, "trench.svg"))


import DraftVecUtils
import draftutils.utils as utils


class TrenchTaskPanel:
    def __init__(self, obj=None):
        self.new = False
        self.obj = obj

        if obj is None:
            import Draft
            self.new = True
            self.obj = Part.Shape()

        self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantTrench.ui"))
        self.form.buttonAddLayer.clicked.connect(self.addLayer)
        self.form.buttonDeleteLayer.clicked.connect(self.removeLayer)
        self.form.buttonUp.clicked.connect(self.moveUp)
        self.form.buttonDown.clicked.connect(self.moveDown)

        self.trenchnode = None
        self.point = None
        self.points = []
        self.linesegments = []
        self.path = None
        self.pos = None
        self.support = None
        self.info = None
        self.tracker = DraftTrackers.wireTracker(
            Part.Wire(Part.makePolygon([FreeCAD.Vector(), FreeCAD.Vector(1, 1, 1)])))

        self.view = FreeCADGui.ActiveDocument.ActiveView
        self.call = self.view.addEventCallback("SoEvent", self.action)

    def action(self, arg):

        if arg["Type"] == "SoKeyboardEvent":
            if arg["Key"] == "ESCAPE":
                self.finish()

        elif arg["Type"] == "SoLocation2Event":
            pos = arg['Position']
            point = FreeCADGui.ActiveDocument.ActiveView.getPoint(pos)
            if len(self.points) > 0:
                pts = self.points.copy()
                pts.append(point)
                self.tracker.updateFromPointlist(pts)

        elif (arg["Type"] == "SoMouseButtonEvent" and
              arg["State"] == "DOWN" and
              arg["Button"] == "BUTTON1"):

            pos = arg['Position']
            listObjects = FreeCADGui.ActiveDocument.ActiveView.getObjectsInfo((int(pos[0]), int(pos[1])))

            if len(listObjects) == 1:
                if listObjects[0]["Object"].startswith("Mesh002"):
                    print("Press on Mesh002. Point: ", pos)
                    self.setPoint(pos)

            elif len(listObjects) > 1:
                find = False
                for object in listObjects:
                    if object["Object"].startswith("TrenchNode"):
                        self.trenchnode = FreeCAD.ActiveDocument.getObject(object["Object"])
                        self.setPoint(self.trenchnode.Placement.Base)
                        find = True
                        break

                    elif object["Object"].startswith("Trench"):
                        tmp = SplitTrench(FreeCAD.ActiveDocument.getObject(object["Object"]),
                                          FreeCADGui.ActiveDocument.ActiveView.getPoint(pos))
                        print(tmp)
                        '''if tmp:
                            self.setPoint(tmp[0])
                            self.trenchnode = tmp[2]'''
                        find = True
                        break

                if not find:
                    if listObjects[0]["Object"].startswith("Mesh002"):
                        self.setPoint(pos)
                    else:
                        self.setPoint(FreeCAD.ActiveDocument.getObject(listObjects[0]["Object"].Name).Placement.Base)

    def setPoint(self, position):
        if isinstance(position, FreeCAD.Vector):
            point = position
        else:
            point = FreeCADGui.ActiveDocument.ActiveView.getPoint(position)

        self.points.append(point)
        if len(self.points) == 1:
            self.tracker.on()
        self.drawSegment()

    def finish(self, close=False, some=False):
        """ Terminate the operation. """

        if len(self.points) > 1:
            import Draft
            self.path = Draft.makeWire(self.points)
            FreeCAD.ActiveDocument.recompute()
            makeTrench(self.path)

    def removeTemporaryObject(self):
        """ Remove temporary object created. """
        self.tracker.finalize()
        sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
        for no in self.linesegments:
            sg.removeChild(no)

    def undolast(self):
        """Undoes last line segment."""

        if len(self.points) > 1:
            self.points.pop()
            sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
            no = self.linesegments.pop()
            sg.removeChild(no)

    def drawSegment(self):
        """Draws new line segment."""

        if len(self.points) > 1:
            self.tracker.updateFromPointlist(self.points)
            """p1 = self.points[-2]
            p2 = self.points[-1]
            print(" linesegment from p1(", p1, ") to p2 (", p2, ")")
            sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
            co = coin.SoCoordinate3()
            pts = [[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z]]
            '''for i in range(len(pts)):
                p = pts[i]
                co.point.set1Value(i, [p.x, p.y, p.z])'''
            co.point.setValues(0, len(pts), pts)
            ma = coin.SoBaseColor()
            ma.rgb = (0, 0, 1)
            st = coin.SoDrawStyle()
            st.style = coin.SoDrawStyle.LINES
            st.lineWidth = 3
            li = coin.SoLineSet()
            li.numVertices.setValue(2)
            no = coin.SoSeparator()
            no.addChild(co)
            no.addChild(ma)
            no.addChild(st)
            no.addChild(li)
            sg.addChild(no)
            self.linesegments.append(no)"""

    def wipe(self):
        """Remove all previous segments and starts from last point."""

        if len(self.points) > 1:
            self.obj.ViewObject.Visibility = False
            self.points = [self.points[-1]]

    def numericInput(self, numx, numy, numz):
        """ Validate the entry fields in the user interface.

        This function is called by the toolbar or taskpanel interface
        when valid x, y, and z have been entered in the input fields.
        """
        self.point = FreeCAD.Vector(numx, numy, numz)
        self.points.append(self.point)
        self.drawSegment()
        self.ui.setNextFocus()

    def addLayer(self):
        num = self.form.listLayers.count() + 1
        self.form.listLayers.addItem("Layer" + str(num))
        # TODO: add property to obj
        layer = "Layer" + str(num)
        self.obj.addProperty("App::PropertyIntegerList",
                             "Name",
                             layer,
                             layer + " Name"
                             )
        setattr(self.obj, "Name", layer)

        self.obj.addProperty("App::PropertyIntegerList",
                             "Description",
                             layer,
                             layer + " description"
                             )

        self.obj.addProperty("App::PropertyIntegerList",
                             "Height",
                             layer,
                             layer + " Height"
                             )
        setattr(self.obj, "Heigth", 100)

    def removeLayer(self):
        # TODO: remove property to obj
        currentRow = self.form.listLayers.currentRow()
        currentItem = self.form.listLayers.takeItem(currentRow)
        del (currentItem)

    def moveUp(self):
        currentRow = self.form.listLayers.currentRow()
        currentItem = self.form.listLayers.takeItem(currentRow)
        self.form.listLayers.insertItem(currentRow - 1, currentItem)

    def moveDown(self):
        currentRow = self.form.listLayers.currentRow()
        currentItem = self.form.listLayers.takeItem(currentRow)
        self.form.listLayers.insertItem(currentRow + 1, currentItem)

    def accept(self):
        FreeCAD.ActiveDocument.openTransaction("Create Trench")
        self.finish()
        FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()

        self.closeForm()
        return True

    def reject(self):
        self.closeForm()
        return False

    def closeForm(self):
        print(" .. Closing .. ")
        self.removeTemporaryObject()
        self.view.removeEventCallback("SoEvent", self.call)
        FreeCADGui.Control.closeDialog()


class semiAutomaticTrench:
    def __init__(self):
        import draftguitools.gui_trackers as DraftTrackers

        self.objects = []
        self.state = 0
        self.direction = None
        self.distance = 1500
        self.point = None
        self.tracker = DraftTrackers.wireTracker(
            Part.Wire(Part.makePolygon([FreeCAD.Vector(), FreeCAD.Vector(1, 1, 1)])))

        # event callbacks
        self._keyPressedCB = None
        self._mouseMovedCB = None
        self._mousePressedCB = None

        self.view = FreeCADGui.activeDocument().activeView()
        self.render_manager = FreeCADGui.ActiveDocument.ActiveView.getViewer().getSoRenderManager()

        #  Callbacks
        self.unregister_editing_callbacks()
        self.register_editing_callbacks()

    # -------------------------------------------------------------------------
    # SCENE EVENTS CALLBACKS
    # -------------------------------------------------------------------------
    def register_editing_callbacks(self):
        """ Register editing callbacks (former action function) """

        print("Registering callbacks")
        if self._keyPressedCB is None:
            self._keyPressedCB = self.view.addEventCallbackPivy(coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed)

        if self._mousePressedCB is None:
            self._mousePressedCB = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),
                                                                  self.mousePressed)

        if self._mouseMovedCB is None:
            self._mouseMovedCB = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.mouseMoved)

    def unregister_editing_callbacks(self):
        """ Remove callbacks used during editing if they exist """

        print("Unregistering callbacks")
        if self._keyPressedCB:
            self.view.removeEventCallbackPivy(coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB)
            self._keyPressedCB = None

        if self._mousePressedCB:
            self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB)
            self._mousePressedCB = None

        if self._mouseMovedCB:
            self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB)
            self._mouseMovedCB = None

    # -------------------------------------------------------------------------
    # SCENE EVENT HANDLERS
    # -------------------------------------------------------------------------
    def keyPressed(self, event_callback):
        event = event_callback.getEvent()
        print(event.getKey(), " - ", event.getState())
        if event.getState() == event.UP:
            if event.getKey() == 65307:  # ESC
                self.quit()
            elif event.getKey() == 65293:  # ENTER
                print("ENTER")
                self.state += 1
                if self.state == 2:
                    '''print("----------------------------------------------------------------")
                    print(" -- objects: ")
                    print(self.objects)
                    print(" -- distance:")
                    print(self.distance)
                    print("----------------------------------------------------------------")'''
                    self.calculateTrenches()
                    self.quit()

            '''elif event.getKey() == ord("q"):  # or event.getKey() == ord(65307):
                if self.obj:
                    self.obj.ViewObject.Proxy.doubleClicked(self.obj.ViewObject)
                else:
                    self.quit()
            elif event.getKey() == ord("s"):
                sel = FreeCADGui.Selection.getSelectionEx()
                tup = None
                if len(sel) == 1:
                    tup = (sel[0].Object, sel[0].SubElementNames)
                for i in range(len(self.selected_objects)):
                    if isinstance(self.selected_objects[i], MarkerOnShape):
                        self.selected_objects[i].sublink = tup
                        # FreeCAD.Console.PrintMessage("Snapped to {}\n".format(str(self.selected_objects[i].sublink)))
                        self.selected_objects[i].drag_start()
                        self.selected_objects[i].drag((0, 0, 0.))
                        self.selected_objects[i].drag_release()
            elif (event.getKey() == 65535) or (event.getKey() == 65288):  # Suppr or Backspace
                # FreeCAD.Console.PrintMessage("Some objects have been deleted\n")
                pts = list()
                for o in self.dynamic_objects:
                    if isinstance(o, MarkerOnShape):
                        pts.append(o)
                self.points = pts
                self.setupInteractionSeparator()'''

    def mousePressed(self, event_callback):
        """ Mouse button event handler, calls: startEditing, endEditing, addPoint, delPoint """
        event = event_callback.getEvent()
        pos = event.getPosition().getValue()
        listObjects = FreeCADGui.ActiveDocument.ActiveView.getObjectsInfo((int(pos[0]), int(pos[1])))

        if event.getButton() == event.BUTTON1:  # left click
            if event.getState() == coin.SoMouseButtonEvent.DOWN:
                if self.state == 0:
                    ''' Select objects '''
                    obj = FreeCAD.ActiveDocument.getObject(listObjects[0]['Object'])
                    if obj in self.objects:
                        self.objects.remove(obj)
                    else:
                        self.objects.append(obj)
                    numobjs = len(self.objects)
                    if numobjs == 0:
                        self.tracker.finalize()
                    elif numobjs == 1:
                        self.tracker.updateFromPointlist([obj.Placement.Base for obj in self.objects])
                        self.tracker.on()
                    else:
                        self.tracker.updateFromPointlist([obj.Placement.Base for obj in self.objects])

                elif self.state == 1:
                    ''' Select distance and direction '''
                    self.direction = FreeCAD.Vector(listObjects[0]['x'], listObjects[0]['y'], listObjects[0]['z'])
                # self.point = None

            elif event.getState() == coin.SoMouseButtonEvent.UP:
                if listObjects and self.point:
                    if self.state == 0:
                        FreeCADGui.Selection.clearSelection()
                        for obj in listObjects:
                            FreeCADGui.Selection.addSelection(obj)

        """if (event.getState() == coin.SoMouseButtonEvent.DOWN) and (
                event.getButton() == event.BUTTON1):  # left click
            print("Mouse button down and mouse button 1")
            if not event.wasAltDown():
                if self.editing is None:
                    ''' do something'''
                else:
                    self.endEditing(self.obj, self.editing)

            elif event.wasAltDown():  # left click with ctrl down
                self.display_tracker_menu(event)

        elif (event.getState() == coin.SoMouseButtonEvent.DOWN) and (
                event.getButton() == event.BUTTON2):  # right click
            self.display_tracker_menu(event)"""

    def mouseMoved(self, event_callback):
        """ Execute as callback for mouse movement. Update tracker position and update preview ghost. """
        if self.state == 1:
            event = event_callback.getEvent()
            pos = event.getPosition().getValue()
            listObjects = FreeCADGui.ActiveDocument.ActiveView.getObjectsInfo((int(pos[0]), int(pos[1])))
            print(listObjects)
            point = FreeCAD.Vector(listObjects[0]['x'], listObjects[0]['y'], listObjects[0]['z'])
            pts = [obj.Placement.Base for obj in self.objects]
            offset = point.sub(pts[0])
            self.tracker.updateFromPointlist([point.add(offset) for point in pts])

    def calculateTrenches(self):
        if len(self.objects) > 1:
            import Draft
            pts = [obj.Placement.Base for obj in self.objects]
            vec = self.direction.sub(pts[0])
            pts1 = [point.add(vec) for point in pts]
            for i in range(len(pts1) - 1):
                makeTrench(Draft.makeLine(pts1[i], pts1[i + 1]))

        return
        pts = [obj.Placement.Base for obj in FreeCADGui.Selection.getSelection()]
        from sklearn.cluster import OPTICS, cluster_optics_dbscan
        import numpy as np
        clust = OPTICS(min_samples=5, xi=0.05, min_cluster_size=0.05)
        X = np.array(pts)

        # Run the fit
        clust.fit(X)

        labels_050 = cluster_optics_dbscan(
            reachability=clust.reachability_,
            core_distances=clust.core_distances_,
            ordering=clust.ordering_,
            eps=0.5,
        )
        labels_200 = cluster_optics_dbscan(
            reachability=clust.reachability_,
            core_distances=clust.core_distances_,
            ordering=clust.ordering_,
            eps=2,
        )

        space = np.arange(len(X))
        reachability = clust.reachability_[clust.ordering_]
        labels = clust.labels_[clust.ordering_]
        print("\n")
        print(" Space: ", space)
        print(" Reachability", reachability)
        print(" Labels", labels)

        return
        from scipy import stats
        xx = list()
        yy = list()
        zz = list()
        for point in pts:
            xx.append(point.x)
            yy.append(point.y)
            zz.append(point.z)
        slope, intercept, r, p, std_err = stats.linregress(xx, yy)

        def myfunc(val):
            return slope * val + intercept

        newzz = list(map(myfunc, [xx[0], xx[-1]]))
        points3D = list()
        points3D.append(FreeCAD.Vector(xx[0], yy[0], newzz[0]))
        points3D.append(FreeCAD.Vector(xx[-1], yy[-1], newzz[1]))

    def quit(self):
        print("Quit")
        self.tracker.finalize()
        self.unregister_editing_callbacks()


'''class CommandTrench:  # V1:
    """Gui command for the Line tool."""

    def GetResources(self):
        """Set icon, menu and tooltip."""
        return {'Pixmap': str(os.path.join(DirIcons, "trench.svg")),
                'MenuText': "Trench",
                'Accel': "C, T",
                'ToolTip': "Creates a Trench object from setup dialog."}

    def IsActive(self):
        active = not (FreeCAD.ActiveDocument is None)
        terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
        active = active and terrain
        if terrain:
            active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
        return active

    def Activated(self):
        """Execute when the command is called."""

        sel = FreeCADGui.Selection.getSelection()
        done = False

        if len(sel) > 0:
            import Draft
            for obj in sel:
                if Draft.getType(obj) == "Wire":
                    FreeCAD.ActiveDocument.openTransaction("Create Trench")
                    makeTrench(obj)
                    FreeCAD.ActiveDocument.commitTransaction()
                    FreeCAD.ActiveDocument.recompute()
                    done = True
                    break

        if not done:
            taskd = TrenchTaskPanel()
            if taskd:
                FreeCADGui.Control.showDialog(taskd)
            else:
                print(" No ha sido posible crear el formulario")


class CommandSemiAutomaticTrench:  # V1:
    """Gui command for the Line tool."""

    def GetResources(self):
        """Set icon, menu and tooltip."""
        return {'Pixmap': str(os.path.join(DirIcons, "trench.svg")),
                'MenuText': "Semi-Automatic Trench Generator",
                'Accel': "T, S",
                'ToolTip': "Creates a Trench object from setup dialog."}

    def IsActive(self):
        active = not (FreeCAD.ActiveDocument is None)
        terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
        active = active and terrain
        if terrain:
            active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
        return active

    def Activated(self):
        """Execute when the command is called."""
        semi = semiAutomaticTrench()


if FreeCAD.GuiUp:
    class CommandTrenchGroup:

        def GetCommands(self):
            return tuple(['PVPlantTrench',
                          'PVPlantSemiAutomaticTrench',
                          ])

        def GetResources(self):
            return {'MenuText': 'Rack Types',
                    'ToolTip': 'Rack Types'
                    }

        def IsActive(self):
            active = not (FreeCAD.ActiveDocument is None)
            terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
            active = active and terrain
            if terrain:
                active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
            return active

    FreeCADGui.addCommand('PVPlantTrench', CommandTrench())
    FreeCADGui.addCommand('PVPlantSemiAutomaticTrench', CommandSemiAutomaticTrench())
    FreeCADGui.addCommand('Trenches', CommandTrenchGroup())'''

