import math
import FreeCAD
from Utils.PVPlantUtils import findObjects

if FreeCAD.GuiUp:
    import FreeCADGui
    from PySide import QtCore, QtGui
    from PySide.QtCore import QT_TRANSLATE_NOOP

    import os
else:
    # \cond
    def translate(ctxt, txt):
        return txt


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

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

import PVPlantResources
from PVPlantResources import DirIcons as DirIcons

field = {"name": "", "width": 0, "heigth": 0}


class exportDXF:

    def __init__(self, filename):
        ''' '''
        self.doc = None
        self.msp = None
        self.filename = filename

        '''
        doc.linetypes.add("GRENZE2",
                          # linetype definition in acad.lin:
                          # A,.25,-.1,[BOX,ltypeshp.shx,x=-.1,s=.1],-.1,1
                          # replacing BOX by shape index 132 (got index from an AutoCAD file),
                          # ezdxf can't get shape index from ltypeshp.shx
                          pattern="A,.25,-.1,[132,ltypeshp.shx,x=-.1,s=.1],-.1,1",
                          description="Grenze eckig ----[]-----[]----[]-----[]----[]--",
                          length=1.45,  # required for complex line types
        })
        
        doc.linetypes.add("GRENZE2",
                        # linetype definition in acad.lin:
                        # A,.25,-.1,[BOX,ltypeshp.shx,x=-.1,s=.1],-.1,1
                        # replacing BOX by shape index 132 (got index from an AutoCAD file),
                        # ezdxf can't get shape index from ltypeshp.shx
                        pattern = "A,.25,-.1,[132,ltypeshp.shx,x=-.1,s=.1],-.1,1",
                        description = "Límite1 ----0-----0----0-----0----0-----0--",
                        length = 1.45,  # required for complex line types
         })
         '''

    def createFile(self, version='R2018'):
        import ezdxf
        from ezdxf.tools.standards import linetypes

        # 1. Create a new document
        self.doc = ezdxf.new(version)
        # 2. Setup document:
        self.doc.header["$INSUNITS"] = 6
        self.doc.header['$MEASUREMENT'] = 1
        self.doc.header['$LUNITS'] = 2
        self.doc.header['$AUNITS'] = 0

        # 3. Add new entities to the modelspace:
        self.msp = self.doc.modelspace()

        print("linetypes: ", self.doc.linetypes)
        for name, desc, pattern in linetypes():
            if name not in self.doc.linetypes:
                self.doc.linetypes.add(name=name,
                                       pattern=pattern,
                                       description=desc,
                                       )


        self.doc.linetypes.add(name="FENCELINE1",
                               pattern="A,.25,-.1,[132,ltypeshp.shx,x=-.1,s=.1],-.1,1",
                               description="Límite1 ----0-----0----0-----0----0-----0--",
                               length=1.45)  # required for complex line types

    def save(self):
        from os.path import exists
        file_exists = exists(self.filename)
        self.doc.saveas(self.filename)
        self.doc.save()

    def createLayer(self, layerName="newLayer", layerColor=(0,0,0), layerLineType='CONTINUOUS'):
        layer = self.doc.layers.add(name=layerName,  linetype=layerLineType)
        layer.rgb = layerColor
        return layer

    def createBlock(self, blockName='newBlock'):
        # Create a block
        block = self.doc.blocks.new(name=blockName)
        return block

    def insertBlock(self, name, point=(0.0, 0.0), rotation=0.0, xscale=0.0, yscale=0.0):
        if name == "":
            return None

        return self.msp.add_blockref(name, point, dxfattribs={
            'xscale': xscale,
            'yscale': yscale,
            'rotation': rotation
        })

    def createPolyline(self, wire):
        data = getWire(wire.Shape)
        lwp = self.msp.add_lwpolyline(data)
        return lwp


def getWire(wire, nospline=False, width=.0):
    """Return a list of DXF ready points and bulges from a wire.

    It builds a list of points from the edges of a `wire`.
    If the edges are circular arcs, the "bulge" of that edge is calculated,
    for other cases, the bulge is considered zero.

    Parameters
    ----------
    wire : Part::TopoShape ('Wire')
        A shape representing a wire.

    nospline : bool, optional
        It defaults to `False`.
        If it is `True`, the edges of the wire are not considered as
        being one of `'BSplineCurve'`, `'BezierCurve'`, or `'Ellipse'`,
        and a simple point is added to the list.
        Otherwise, `getSplineSegs(edge)` is used to extract
        the points and add them to the list.

    Returns
    -------
    list of tuples
        It returns a list of tuples ``[(...), (...), ...]``
        where each tuple indicates a point with additional information
        besides the coordinates.
        Two types of tuples may be returned.

    [(float, float, float, None, None, float), ...]
        When `lw` is `True` (`'lwpolyline'`)
        the first three values represent the coordinates of the point,
        the next two are `None`, and the last value is the bulge.

    [((float, float, float), None, [None, None], float), ...]
        When `lw` is `False` (`'polyline'`)
        the first element is a tuple of three values that indicate
        the coordinates of the point, the next element is `None`,
        the next element is a list of two `None` values,
        and the last element is the value of the bulge.

    See also
    --------
    calcBulge
    """
    import Part
    import DraftGeomUtils
    import math

    def fmt(vec, b=0.0):
        return (vec.x * 0.001, vec.y * 0.001, width, width, b)

    points = []
    edges = Part.__sortEdges__(wire.Edges)
    for edge in edges:
        v1 = edge.Vertexes[0].Point
        if DraftGeomUtils.geomType(edge) == "Circle":
            # polyline bulge -> negative makes the arc go clockwise
            angle = edge.LastParameter - edge.FirstParameter
            bul = math.tan(angle / 4)
            if edge.Curve.Axis.dot(FreeCAD.Vector(0, 0, 1)) < 0:
                bul = -bul
            points.append(fmt(v1, bul))
        elif (DraftGeomUtils.geomType(edge) in ["BSplineCurve",
                                                "BezierCurve",
                                                "Ellipse"]) and (not nospline):
            spline = getSplineSegs(edge)
            spline.pop()
            for p in spline:
                points.append(fmt(p))
        else:
            points.append(fmt(v1))

    v = edges[-1].Vertexes[-1].Point
    points.append(fmt(v))

    return points


def getArcData(edge):
    """Return center, radius, start, and end angles of a circle-based edge.

    Parameters
    ----------
    edge : Part::TopoShape ('Edge')
        An edge representing a circular arc, either open or closed.

    Returns
    -------
    (tuple, float, float, float)
        It returns a tuple of four values; the first value is a tuple
        with the coordinates of the center `(x, y, z)`;
        the other three represent the magnitude of the radius,
        and the start and end angles in degrees that define the arc.

    (tuple, float, 0, 0)
        If the number of vertices in the `edge` is only one, only the center
        point exists, so it's a full circumference; in this case, both
        angles are zero.
    """
    ce = edge.Curve.Center
    radius = edge.Curve.Radius
    if len(edge.Vertexes) == 1:
        # closed circle
        return DraftVecUtils.tup(ce), radius, 0, 0
    else:
        # new method: recalculate ourselves as we cannot trust edge.Curve.Axis
        # or XAxis
        p1 = edge.Vertexes[0].Point
        p2 = edge.Vertexes[-1].Point
        v1 = p1.sub(ce)
        v2 = p2.sub(ce)
        # print(v1.cross(v2))
        # print(edge.Curve.Axis)
        # print(p1)
        # print(p2)
        # we can use Z check since arcs getting here will ALWAYS be in XY plane
        # Z can be 0 if the arc is 180 deg
        # if (v1.cross(v2).z >= 0) or (edge.Curve.Axis.z > 0):
        # Calculates the angles of the first and last points
        # in the circular arc, with respect to the global X axis.
        if edge.Curve.Axis.z > 0:
            # clockwise
            ang1 = -DraftVecUtils.angle(v1)
            ang2 = -DraftVecUtils.angle(v2)
        else:
            # counterclockwise
            ang2 = -DraftVecUtils.angle(v1)
            ang1 = -DraftVecUtils.angle(v2)

        # obsolete method - fails a lot
        # if round(edge.Curve.Axis.dot(Vector(0, 0, 1))) == 1:
        #    ang1, ang2 = edge.ParameterRange
        # else:
        #    ang2, ang1 = edge.ParameterRange
        # if edge.Curve.XAxis != Vector(1, 0, 0):
        #    ang1 -= DraftVecUtils.angle(edge.Curve.XAxis)
        #    ang2 -= DraftVecUtils.angle(edge.Curve.XAxis)

        return (DraftVecUtils.tup(ce), radius,
                math.degrees(ang1), math.degrees(ang2))


class _PVPlantExportDXF(QtGui.QWidget):
    '''The editmode TaskPanel to select what you want to export'''

    def __init__(self):
        # super(_PVPlantExportDXF, self).__init__()
        QtGui.QWidget.__init__(self)

        import os
        # self.form:
        self.form = FreeCADGui.PySideUic.loadUi(
            os.path.join(os.path.dirname(os.path.realpath(__file__)), "exportDXF.ui"))
        # setWindowIcon(QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "convert.svg")))
        # self.form.buttonTo.clicked.connect(self.addTo)

        self.layout = QtGui.QHBoxLayout(self)
        self.layout.setContentsMargins(4, 4, 4, 4)
        self.layout.addWidget(self.form)

        self.form.buttonAcept.clicked.connect(self.onAceptClick)

        path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "autocad")
        if not os.path.exists(path):
            os.makedirs(path)
        self.filename = os.path.join(path, FreeCAD.ActiveDocument.Name) + ".dxf"

    def createLayers(self):
        ''' '''
        self.exporter.createLayer("Areas_Boundary", layerColor=(0, 125, 125), layerLineType="FENCELINE1")
        self.exporter.createLayer("Areas_Exclusion", layerColor=(255, 0, 0))
        self.exporter.createLayer("Areas_Offsets", layerColor=(128, 128, 255))

        self.exporter.createLayer("Internal_Roads", layerColor=(128, 128, 128))
        self.exporter.createLayer("Internal_Roads_Axis", layerColor=(255, 255, 255), layerLineType="DASHEDX2")


    def writeArea(self):
        for area in FreeCAD.ActiveDocument.Boundary.Group:
            pol = self.exporter.createPolyline(area)
            pol.dxf.layer = "Areas_Boundary"

        for area in FreeCAD.ActiveDocument.Exclusion.Group:
            pol = self.exporter.createPolyline(area)
            pol.dxf.layer = "Areas_Exclusion"

        for area in FreeCAD.ActiveDocument.Offsets.Group:
            self.exporter.createPolyline(area)
            pol.dxf.layer = "Areas_Offsets"

    def writeFrameSetups(self):
        import Part
        # 1. Profiles:
        profilelist = list()
        for ts in FreeCAD.ActiveDocument.Site.Frames:
            for poletype in ts.PoleType:
                if not (poletype in profilelist):
                    profilelist.append(poletype)
                    block = self.exporter.createBlock(poletype.Label)
                    w = poletype.Base.Shape.Wires[0]
                    w.Placement.Base = FreeCAD.Vector(w.Placement.Base).sub(w.BoundBox.Center)
                    block.add_lwpolyline(getWire(w))

                    block.add_circle((0, 0), 0.2, dxfattribs={'color': 2})
                    p = math.sin(math.radians(45)) * 0.2

                    block.add_line((-p, -p), (p, p), dxfattribs={"layer": "MyLines"})
                    block.add_line((-p, p), (p, -p), dxfattribs={"layer": "MyLines"})

        # 2. Frames
        for ts in FreeCAD.ActiveDocument.Site.Frames:
            w = max(ts.Shape.SubShapes[0].SubShapes[0].SubShapes[0].Faces, key=lambda x: x.Area)
            pts = [w.BoundBox.getPoint(i) for i in range(4)]
            pts.append(FreeCAD.Vector(pts[0]))
            w = Part.makePolygon(pts)
            w.Placement.Base = w.Placement.Base.sub(w.BoundBox.Center)
            mblockname = "Trina_TSM-DEG21C-20-6XXWp Vertex"
            mblock = self.exporter.createBlock(mblockname)
            mblock.add_lwpolyline(getWire(w))

            rblock = self.exporter.createBlock(ts.Label)
            w = max(ts.Shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda x: x.Placement.Base.z).Wires[0]
            w.Placement.Base = w.Placement.Base.sub(w.BoundBox.Center)
            rblock.add_lwpolyline(getWire(w))
            for module in ts.Shape.SubShapes[0].SubShapes[0].SubShapes:
                point = FreeCAD.Vector(module.BoundBox.Center) * 0.001
                point = point[:2]
                rblock.add_blockref(mblockname, point, dxfattribs={
                    'xscale': .0,
                    'yscale': .0,
                    'rotation': 0})
            for ind in range(int(ts.NumberPole.Value)):
                point = ts.Shape.SubShapes[1].SubShapes[0].SubShapes[ind].Placement.Base * 0.001
                point = point[:2]
                name = ts.PoleType[ts.PoleSequence[ind]].Label
                rblock.add_blockref(name, point, dxfattribs={
                    'xscale': .0,
                    'yscale': .0,
                    'rotation': 0})

    def writeFrames(self):
        objects = findObjects('Tracker')
        for frame in objects:
            if hasattr(frame, "Setup"):
                point = frame.Placement.Base * 0.001
                point = point[:2]
                self.exporter.insertBlock(frame.Setup.Label, point=point, rotation=frame.AngleZ)

    def writeRoads(self):
        objects = findObjects("Road")
        #rblock = self.exporter.createBlock("Internal_roads")
        for road in objects:
            base = self.exporter.createPolyline(road.Base)
            base.dxf.const_width = road.Width.Value * 0.001
            base.dxf.layer = "Internal_Roads"

            axis = self.exporter.createPolyline(road.Base)
            axis.dxf.const_width = .2
            axis.dxf.layer = "Internal_Roads_Axis"
            #my_lines = doc.layers.get('MyLines')

    def writeTrenches(self):
        objects = findObjects("Trench")
        # rblock = self.exporter.createBlock("Internal_roads")
        for obj in objects:
            base = self.exporter.createPolyline(obj.Base)
            base.dxf.const_width = obj.Width.Value * 0.001
            base.dxf.layer = "Trench"

    def setup_layout4(self, doc):
        layout2 = doc.layouts.new("scale 1-1")
        # The default paperspace scale is 1:1
        # 1 mm printed is 1 drawing unit in paperspace
        # For most use cases this is the preferred scaling and important fact:
        # the paperspace scaling has no influence on the VIEWPORT scaling - this is
        # a total different topic, see example "viewports_in_paperspace.py"

        layout2.page_setup(size=(297, 210),
                           margins=(10, 10, 10, 10),
                           units="mm",
                           scale=(1, 1),
                           #offset=(50, 50),
                           )
        layout2.add_viewport(
            # center of viewport in paperspace units
            center=(100, 100),
            # viewport size in paperspace units
            size=(50, 50),
            # modelspace point to show in center of viewport in WCS
            view_center_point=(60, 40),
            # how much modelspace area to show in viewport in drawing units
            view_height=20,
            #status=2,
        )
        lower_left, upper_right = layout2.get_paper_limits()
        x1, y1 = lower_left
        x2, y2 = upper_right
        center = lower_left.lerp(upper_right)

        # Add DXF entities to the "Layout1" in paperspace coordinates:
        layout2.add_line((x1, center.y), (x2, center.y))  # horizontal center line
        layout2.add_line((center.x, y1), (center.x, y2))  # vertical center line
        layout2.add_circle((0, 0), radius=5)  # plot origin

    def onAceptClick(self):
        self.exporter = exportDXF(self.filename)
        if self.exporter:
            self.exporter.createFile()
            self.createLayers()
            self.writeArea()
            self.writeFrameSetups()
            self.writeFrames()
            self.writeRoads()
            self.writeTrenches()
            self.setup_layout4(self.exporter.doc)

        self.exporter.save()
        print(self.filename)

        self.close()


class _CommandExportDXF:
    def GetResources(self):
        return {'Pixmap': str(os.path.join(DirIcons, "dxf.svg")),
                'Accel': "E, A",
                'MenuText': "Export to DXF",
                'ToolTip': QT_TRANSLATE_NOOP("Placement", "Export choosed layers to dxf")}

    def Activated(self):
        taskd = _PVPlantExportDXF()
        taskd.setParent(FreeCADGui.getMainWindow())
        taskd.setWindowFlags(QtCore.Qt.Dialog or QtCore.Qt.Dialog)
        taskd.setWindowModality(QtCore.Qt.WindowModal)
        taskd.show()

    def IsActive(self):
        if FreeCAD.ActiveDocument:
            return True
        else:
            return False


if FreeCAD.GuiUp:
    FreeCADGui.addCommand('exportDXF', _CommandExportDXF())
