
import math

import ArchComponent
import FreeCAD
import Part
import random
from FreeCAD import Qt
from PySide.QtCore import QT_TRANSLATE_NOOP

try:
    from scipy import spatial
    has_scipy = True
except ImportError:
    has_scipy = False

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 parametric tree object for architectural design"""

    def __init__(self, obj):
        ArchComponent.Component.__init__(self, obj)
        self.obj = obj
        self.setProperties(obj)
        random.seed(42)  # Semilla para resultados consistentes

    def setProperties(self, obj):
        """Define y configura las propiedades del objeto"""
        pl = obj.PropertiesList

        # Propiedades de la copa
        canopy_props = [
            ("CanopyHeight", "App::PropertyLength", "Altura total de la copa"),
            ("CanopyRadius", "App::PropertyLength", "Radio máximo de la copa"),
            ("Spikiness", "App::PropertyFloatConstraint", "Irregularidad de la superficie", (0.5, 0.0, 1.0, 0.05)),
            (
            "CrownExpansion", "App::PropertyFloatConstraint", "Expansión de la corona superior", (1.0, 0.0, 2.0, 0.05)),
            ("UmbrellaEffect", "App::PropertyFloatConstraint", "Efecto de dosel/paraguas", (0.0, 0.0, 1.0, 0.05)),
            ("LeafCount", "App::PropertyInteger", "Densidad de follaje (número de segmentos)")
        ]

        for prop in canopy_props:
            name, ptype, doc, *args = prop
            if name not in pl:
                if ptype == "App::PropertyFloatConstraint":
                    obj.addProperty(ptype, name, "Canopy", doc).__setattr__(name, args[0])
                else:
                    obj.addProperty(ptype, name, "Canopy", doc)
                # Valores por defecto
                if name == "LeafCount":
                    setattr(obj, name, 20)
                elif name in ["CanopyHeight", "CanopyRadius"]:
                    setattr(obj, name, 4000 if "Height" in name else 1500)

        # Propiedades del tronco
        trunk_props = [
            ("TrunkHeight", "App::PropertyLength", "Altura del tronco", 2000),
            ("TrunkRadius", "App::PropertyLength", "Radio base del tronco", 150),
            ("TrunkFaces", "App::PropertyInteger", "Caras del tronco", 6)
        ]

        for prop in trunk_props:
            name, ptype, doc, default = prop
            if name not in pl:
                obj.addProperty(ptype, name, "Trunk", doc)
                setattr(obj, name, default)

        # Propiedades base
        if "Type" not in pl:
            obj.addProperty("App::PropertyString", "Type", "Base", "Tipo de objeto").Type = "Vegetable-Tree"
            obj.setEditorMode("Type", 1)  # Hacerla de solo lectura

        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):
        """Actualiza la forma cuando cambian propiedades"""
        if prop in ["CanopyHeight", "CanopyRadius", "Spikiness", "CrownExpansion",
                    "UmbrellaEffect", "LeafCount", "TrunkHeight", "TrunkRadius", "TrunkFaces"]:
            self.execute(obj)

    def createTrunk(self, obj):
        """Crea la geometría del tronco usando un loft"""
        try:
            # Calcula dimensiones proporcionales
            base_radius = obj.TrunkRadius.Value
            top_radius = base_radius * 0.8
            height = obj.TrunkHeight.Value

            # Crea tres perfiles circulares
            profiles = []
            for z, radius in [(0, base_radius),
                              (height / 3, base_radius),
                              (height, top_radius)]:
                circle = Part.makeCircle(radius, FreeCAD.Vector(0, 0, z))
                profiles.append(Part.Wire([circle]))

            return Part.makeLoft(profiles, True, True)
        except Exception as e:
            FreeCAD.Console.PrintError(f"Error creando tronco: {str(e)}\n")
            return None

    def createCanopy(self, obj):
        """Genera la forma de la copa usando una envoltura convexa"""
        if not has_scipy:
            FreeCAD.Console.PrintError("Scipy no está instalado. No se puede generar la copa.\n")
            return None

        try:
            # Configuración inicial
            n_segments = max(3, obj.LeafCount)  # Mínimo 3 segmentos
            radius = obj.CanopyRadius.Value
            height = obj.CanopyHeight.Value

            # Genera puntos distribuidos esféricamente con ruido
            points = []
            for _ in range(n_segments * 10):  # 10 puntos por segmento
                theta = random.uniform(0, 2 * math.pi)
                phi = math.acos(random.uniform(-1, 1))

                # Aplica parámetros de forma
                r = radius * (1 - obj.Spikiness * random.random())
                x = r * math.sin(phi) * math.cos(theta)
                y = r * math.sin(phi) * math.sin(theta)
                z = height * (0.5 + 0.5 * math.cos(phi))  # Distribución vertical

                # Aplica efectos de forma
                z *= (1 - obj.UmbrellaEffect)
                if z > height / 2:
                    x *= obj.CrownExpansion
                    y *= obj.CrownExpansion

                points.append(FreeCAD.Vector(x, y, z))

            # Crea la envoltura convexa
            hull = spatial.ConvexHull([(p.x, p.y, p.z) for p in points])
            faces = []
            for simplex in hull.simplices:
                triangle = [points[i] for i in simplex]
                faces.append(Part.Face(Part.makePolygon(triangle + [triangle[0]])))

            return Part.Compound(faces)
        except Exception as e:
            FreeCAD.Console.PrintError(f"Error creando copa: {str(e)}\n")
            return None

    def execute(self, obj):
        """Ensambla el objeto final"""
        try:
            # Crea componentes
            trunk = self.createTrunk(obj)
            canopy = self.createCanopy(obj)

            # Verifica componentes válidos
            if not trunk or not canopy:
                raise ValueError("Error en la generación de componentes")

            # Posiciona la copa sobre el tronco
            canopy_placement = FreeCAD.Placement()
            canopy_placement.Base.z = obj.TrunkHeight.Value
            canopy.Placement = canopy_placement

            # Combina las formas
            compound = Part.Compound([trunk, canopy])
            obj.Shape = compound

            # Configura apariencia
            if obj.ViewObject:
                obj.ViewObject.DiffuseColor = ([(0.35, 0.2, 0.05)] * len(trunk.Faces) +
                                               [(0.1, 0.6, 0.2)] * len(canopy.Faces))  # Color copa

        except Exception as e:
            FreeCAD.Console.PrintError(f"Error al ejecutar: {str(e)}\n")


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

