
import math

import ArchComponent
import FreeCAD

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

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


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

__title__ = "FreeCAD Fixed Rack"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"

__dir__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "PVPlant")
DirResources = os.path.join(__dir__, "Resources")
DirIcons = os.path.join(DirResources, "Icons")
DirImages = os.path.join(DirResources, "Images")


def makeTree():
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Tree")
    Tree(obj)
    ViewProviderTree(obj.ViewObject)
    FreeCAD.ActiveDocument.recompute()

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

    return obj


class Tree(ArchComponent.Component):
    """ A Shadow Tree Obcject """

    def __init__(self, obj):
        # Definición de variables:
        ArchComponent.Component.__init__(self, obj)
        self.obj = obj
        self.setProperties(obj)

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

        # CANOPY: ---------------------------------------------------------
        if not ("CanopyHeight" in pl):
            obj.addProperty("App::PropertyLength",
                            "CanopyHeight",
                            "Canopy",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).CanopyHeight = 4000

        if not ("CanopyRadius" in pl):
            obj.addProperty("App::PropertyLength",
                            "CanopyRadius",
                            "Canopy",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).CanopyRadius = 1500

        if not ("Spikiness" in pl):
            obj.addProperty("App::PropertyFloatConstraint",
                            "Spikiness",
                            "Canopy",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).Spikiness = (0.5, 0.0, 1.0, 0.05)  # (Default, Start, Finish, Step)

        '''
        if not ("Lumpiness" in pl):
            obj.addProperty("App::PropertyFloatConstraint",
                            "Lumpiness",
                            "Canopy",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).Lumpiness = (0.0, 0.0, 1.0, 0.05) #(Default, Start, Finish, Step)'''

        if not ("CrownExpansion" in pl):
            obj.addProperty("App::PropertyFloatConstraint",
                            "CrownExpansion",
                            "Canopy",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).CrownExpansion = (1.0, 0.0, 2.0, 0.05)  # (Default, Start, Finish, Step)

        if not ("UmbrellaEffect" in pl):
            obj.addProperty("App::PropertyFloatConstraint",
                            "UmbrellaEffect",
                            "Canopy",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).UmbrellaEffect = (0.0, 0.0, 1.0, 0.05)  # (Default, Start, Finish, Step)

        if not ("LeafCount" in pl):
            obj.addProperty("App::PropertyQuantity",
                            "LeafCount",
                            "Canopy",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).LeafCount = 20

        # TRUNK: ------------------------------------------------------------------------------------------------------
        if not ("TrunkHeight" in pl):
            obj.addProperty("App::PropertyLength",
                            "TrunkHeight",
                            "Trunk",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).TrunkHeight = 2000

        if not ("TrunkRadius" in pl):
            obj.addProperty("App::PropertyLength",
                            "TrunkRadius",
                            "Trunk",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).TrunkRadius = 150

        if not ("TrunkFaces" in pl):
            obj.addProperty("App::PropertyQuantity",
                            "TrunkFaces",
                            "Trunk",
                            QT_TRANSLATE_NOOP("App::Property", "The height of self object")
                            ).TrunkFaces = 6

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

        self.Type = obj.Type
        obj.Proxy = self
        obj.IfcType = "Shading Device"
        obj.setEditorMode("IfcType", 1)

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

    def onChanged(self, obj, prop):
        '''if prop in ["CanopyHeight", "CanopyHeight", "Spikiness", "CrownExpansion", "UmbrellaEffect",
                    "LeafCount"]:
            self.canopy = self.createCanopy(obj)

        if prop in ["TrunkHeight", "TrunkRadius", "TrunkFaces"]:
            self.trunk = self.createTrunk(obj)'''

    def createTrunk(self, obj):
        import Part
        angle = (math.pi * 2) / obj.TrunkFaces.Value
        delta = obj.TrunkRadius.Value
        pts = [FreeCAD.Vector(delta, 0, 0)]
        for i in range(int(obj.TrunkFaces.Value) - 1):
            ang = (i + 1) * angle
            pts.append(FreeCAD.Vector(delta * math.cos(ang),
                                      delta * math.sin(ang),
                                      0))
        pts.append(pts[0])
        p1 = Part.makePolygon(pts)
        p0 = p1.makeOffset2D(90, 2, False, False, True)
        p2 = p1.makeOffset2D(-50, 2, False, False, True)
        p0.Placement.Base.z = -150
        p1.Placement.Base.z = 150
        p2.Placement.Base.z = obj.TrunkHeight.Value - 250
        return Part.makeLoft([p0, p1, p2], True, True, False)

    def createCanopy(self, obj):
        import Part
        import random
        import Mesh
        import numpy as np

        ncircles = int(obj.LeafCount.Value)
        if ncircles % 2 == 0:
            ncircles += 1
        half_ncircles = int(ncircles / 2)
        ncirclesumbrella = int(half_ncircles/2)
        ncirclestop = ncircles - ncirclesumbrella

        # 1. Create circles to define the sphere
        circles = []
        dist = 2 * obj.CanopyRadius.Value / (ncircles - 1)
        margin = dist * 0.01
        '''for i in range(half_ncircles + 1):
            if i > 0:
                d = (obj.CanopyRadius.Value - dist * i)
            else:
                d = obj.CanopyRadius.Value - margin
            r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5
            c = Part.makeCircle(r)
            circles.append(c)

        ctmp = [c.copy() for c in circles]
        ctmp.pop()
        ctmp.reverse()
        circles.extend(ctmp)'''

        d = - obj.CanopyRadius.Value - dist
        b = (obj.CanopyRadius.Value ** 2 - (dist * (ncirclesumbrella - 1)) ** 2) ** 0.5

        for i in range(ncircles):
            d += dist
            r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5
            if i > ncirclesumbrella:
                if obj.CrownExpansion < 1:
                    r = r * obj.CrownExpansion
            if r == 0:
                r = obj.CanopyRadius.Value * 0.01
            c = Part.makeCircle(r)
            circles.append(c)

        # 2. Place circles
        dist = obj.CanopyHeight.Value / ncircles
        z = 0
        #zmax = dist * half_ncircles
        for idx in range(1, half_ncircles):
            z += dist
            circles[idx].Placement.Base.z = (1 - obj.UmbrellaEffect) * z
            #c.Placement.Base.z = obj.UmbrellaEffect * (zmax - z) + z

        dist1 = (obj.CanopyHeight.Value - z) / (half_ncircles + 1)
        for idx in range(half_ncircles, ncircles):
            c = circles[idx]
            z += dist1
            c.Placement.Base.z = z

        # 3. noise generator
        pts = []
        val = (dist / 2 - margin) * obj.Spikiness
        for c in circles:
            tmppts = c.discretize(ncircles)
            for j in range(len(tmppts)):
                point = tmppts[j]
                point.x += random.uniform(-val, val)
                point.y += random.uniform(-val, val)
                point.z += random.uniform(-val, val)
                pts.append(point)

        # 4. generate the mesh / solid
        from scipy import spatial as sp_spatial
        pts = np.array(pts)
        hull = sp_spatial.ConvexHull(pts)
        indices = hull.simplices
        faces = pts[indices]
        mesh = Mesh.Mesh(faces.tolist())
        if len(mesh.Facets) == 0:
            return None
        mesh.harmonizeNormals()
        '''if mesh.Facets[0].Normal.z < 0:
            mesh.flipNormals()'''
        shape = Part.Shape()
        shape.makeShapeFromMesh(mesh.Topology, 0.1)
        return Part.makeSolid(shape)

    def execute(self, obj):
        pl = obj.Placement

        trunk = self.createTrunk(obj)
        canopy = self.createCanopy(obj)
        canopy.Placement.Base.z = obj.TrunkHeight.Value - 250  # - obj.CanopyRadius.Value / 2
        obj.Shape = Part.makeCompound([trunk, canopy])
        obj.Placement = pl

        color = [(0.2510, 0.1255, 0.0)] * len(trunk.Faces)
        color.extend([(0.0, 0.3922, 0.0)] * len(canopy.Faces))
        obj.ViewObject.DiffuseColor = color


class ViewProviderTree(ArchComponent.ViewProviderComponent):
    "A View Provider for the Pipe object"

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

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


class TreeTaskPanel(QtGui.QWidget):
    def __init__(self, obj=None):
        QtGui.QWidget.__init__(self)
        self.obj = obj
        if self.obj is None:
            self.obj = makeTree()

        self.form = FreeCADGui.PySideUic.loadUi(__dir__ + "/PVPlantTree.ui")
        self.layout = QtGui.QHBoxLayout(self)
        self.layout.setContentsMargins(4, 4, 4, 4)
        self.layout.addWidget(self.form)

        self.form.editCanopyHeight.valueChanged.connect(self.Canopy)
        self.form.editCanopyRadius.valueChanged.connect(self.Canopy)
        self.form.editSpikiness.valueChanged.connect(self.Canopy)
        self.form.editCrownExpansion.valueChanged.connect(self.Canopy)
        self.form.editLeftUmbrellaEffect.valueChanged.connect(self.Canopy)
        self.form.editLeafCount.valueChanged.connect(self.Canopy)

    def Canopy(self):
        self.obj.CanopyHeight = FreeCAD.Units.Quantity(self.form.editCanopyHeight.text()).Value
        self.obj.CanopyRadius = FreeCAD.Units.Quantity(self.form.editCanopyRadius.text()).Value
        self.obj.Spikiness = self.form.editSpikiness.value()
        self.obj.CrownExpansion = self.form.editCrownExpansion.value()
        self.obj.UmbrellaEffect = self.form.editLeftUmbrellaEffect.value()
        self.obj.LeafCount = self.form.editLeafCount.value()

        FreeCAD.ActiveDocument.recompute()

    def accept(self):
        FreeCADGui.Control.closeDialog()
        return True

    def reject(self):
        FreeCAD.ActiveDocument.removeObject(self.obj.Name)
        FreeCADGui.Control.closeDialog()
        return True


class _CommandTree:
    "the PVPlant Tree command definition"

    def GetResources(self):
        return {'Pixmap': str(os.path.join(DirIcons, "tree(1).svg")),
                'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTree", "Tree"),
                'Accel': "S, T",
                'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTree",
                                                    "Creates a Tree object from setup dialog.")}

    def IsActive(self):
        return not FreeCAD.ActiveDocument is None

    def Activated(self):
        import draftguitools.gui_trackers as DraftTrackers
        self.tree = makeTree()
        FreeCADGui.Snapper.getPoint(callback=self.getPoint,
                                    movecallback=self.mousemove,
                                    extradlg=self.taskbox(),
                                    title="Position of the tree:")

    def getPoint(self, point=None, obj=None):
        self.tree.Placement.Base = point
        FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()
        self.tracker.finalize()

    def mousemove(self, pt, snapInfo):
        self.tree.Placement.Base = pt

    def taskbox(self):
        self.form = TreeTaskPanel(self.tree)
        return self.form


if FreeCAD.GuiUp:
    FreeCADGui.addCommand('PVPlantTree', _CommandTree())

