from salome.shaper import model
import ModelAPI
import traceback
import math

class SplitCylinder(model.Feature):

    def __init__(self):
        model.Feature.__init__(self)

    @staticmethod
    def ID():
        return "SplitCylinder"

    @staticmethod
    def SOLID_ID():
        return "solid_body"

    @staticmethod
    def FACE_ID():
        return "face"

    @staticmethod
    def IS_PERCENTAGE_ID():
        return "is_percentage"

    @staticmethod
    def SIZE_INPUT_ID():
        return "size_input"

    def getKind(self):
        return SplitCylinder.ID()

    def initAttributes(self):
        self.data().addAttribute(self.SOLID_ID(), ModelAPI.ModelAPI_AttributeSelection_typeId())
        self.data().addAttribute(self.FACE_ID(), ModelAPI.ModelAPI_AttributeSelection_typeId())
        self.data().addAttribute(self.IS_PERCENTAGE_ID(), ModelAPI.ModelAPI_AttributeBoolean_typeId())
        self.data().addAttribute(self.SIZE_INPUT_ID(), ModelAPI.ModelAPI_AttributeDouble_typeId())

    def execute(self):
        try:
            self._execute_safe()
        except Exception as e:
            print("\n" + "="*50)
            print("ERROR IN SPLIT CYLINDER MACRO:")
            traceback.print_exc()
            print("="*50 + "\n")
            return

    def _execute_safe(self):
        doc = self.document()

        # =========================================================
        # 1. LIMPIEZA DE PREVISUALIZACIÓN ANTERIOR
        # =========================================================
        if not hasattr(self, "_preview_names"):
            self._preview_names = []
            
        if self._preview_names:
            for f_name in reversed(self._preview_names):
                try:
                    feat = doc.feature(f_name)
                    if feat:
                        model.deleteFeatures(doc, [model.selection(f_name)])
                except:
                    pass
            self._preview_names = []

        # =========================================================
        # 2. VERIFICAR SELECCIONES ACTUALES
        # =========================================================
        solid_attr = self.selection(self.SOLID_ID())
        if solid_attr is None or not solid_attr.isInitialized(): return
        solid_name = solid_attr.namingName()
        if not solid_name: return

        face_attr = self.selection(self.FACE_ID())
        if face_attr is None or not face_attr.isInitialized(): return
        face_name = face_attr.namingName()
        if not face_name: return

        # =========================================================
        # 3. SISTEMA DE CACHÉ (Evita fallos de lectura al actualizar)
        # =========================================================
        # Si cambiamos de cilindro, o es la primera vez, extraemos y memorizamos.
        if not hasattr(self, "_cached_selections") or self._cached_selections != (solid_name, face_name):
            
            solid_shape = solid_attr.value()
            face_shape = face_attr.value()
            
            R = 10.0  
            H = 50.0
            try:
                from GeomAPI import GeomAPI_Face, GeomAPI_ShapeExplorer, GeomAPI_Shape
                exp = GeomAPI_ShapeExplorer(solid_shape, GeomAPI_Shape.FACE)
                while exp.more():
                    try:
                        f = GeomAPI_Face(exp.current())
                        cyl = f.getCylinder()
                        R = cyl.radius()
                        H = cyl.height()
                        break
                    except:
                        pass
                    exp.next()
            except Exception as e:
                pass

            cx, cy, cz = 0.0, 0.0, 0.0
            try:
                mid = face_shape.middlePoint()
                try: cx, cy, cz = mid.X(), mid.Y(), mid.Z()
                except: cx, cy, cz = mid.x(), mid.y(), mid.z()
            except: pass
            
            # ¡Memorizamos los datos perfectos!
            self._cached_R = R
            self._cached_H = H
            self._cached_center = (cx, cy, cz)
            self._cached_selections = (solid_name, face_name)

        # Usamos los datos guardados en la memoria de la macro
        R = self._cached_R
        H = self._cached_H
        cx, cy, cz = self._cached_center

        # =========================================================
        # 4. CÁLCULO DE TAMAÑO (Actualización en tiempo real)
        # =========================================================
        is_perc_attr = self.boolean(self.IS_PERCENTAGE_ID())
        is_percentage = is_perc_attr.value() if (is_perc_attr and is_perc_attr.isInitialized()) else False

        input_attr = self.real(self.SIZE_INPUT_ID())
        if input_attr is None or not input_attr.isInitialized(): return
        user_input = input_attr.value()
        if user_input <= 0: return

        if is_percentage:
            # Ahora R siempre es exacto (ej. 5). Diámetro = 10. El 20% será 2.
            math_side = (2.0 * R) * (user_input / 100.0)
        else:
            math_side = user_input

        # LÍMITE DE SEGURIDAD
        max_side = (R * math.sqrt(2.0)) - 0.01
        if math_side > max_side:
            math_side = max_side 

        h = math_side / 2.0

        # =========================================================
        # 5. CREACIÓN DE GEOMETRÍA (Registrando nombres dinámicos)
        # =========================================================
        center_3d = model.addPoint(doc, cx, cy, cz)
        center_3d.execute(True)
        try: self._preview_names.append(center_3d.name())
        except: pass

        sketch = model.addSketch(doc, model.selection("FACE", face_name))
        proj_c = sketch.addProjection(model.selection(center_3d.defaultResult()), False)
        proj_c.execute(True)
        c_feat = proj_c.createdFeature()
        
        center_2d = None
        if c_feat is not None:
            c_feat.execute(True)
            center_2d = c_feat.result()

        p0 = sketch.addPoint(0, 0)
        p0.setAuxiliary(True)
        p0.execute(True)

        if center_2d is not None:
            try: sketch.setCoincident(p0.coordinates(), center_2d, True)
            except: pass

        sq1 = sketch.addLine(-h, h, -h, -h)   
        sq2 = sketch.addLine(-h, -h, h, -h)   
        sq3 = sketch.addLine(h, -h, h, h)     
        sq4 = sketch.addLine(h, h, -h, h)     
        
        c1 = sketch.addLine(-h, h, h, -h); c1.setAuxiliary(True) 
        c2 = sketch.addLine(-h, -h, h, h); c2.setAuxiliary(True) 
        
        L_ext = R - (h * 1.414213562)
        if L_ext < 0.1: L_ext = 0.1 
            
        d1 = sketch.addLine(h, h, h+1, h+1)
        d2 = sketch.addLine(-h, h, -h-1, h+1)
        d3 = sketch.addLine(-h, -h, -h-1, -h-1)
        d4 = sketch.addLine(h, -h, h+1, -h-1)

        for line in [sq1, sq2, sq3, sq4, c1, c2, d1, d2, d3, d4]: line.execute(True)

        sketch.setCoincident(sq4.endPoint(), sq1.startPoint(), True)
        sketch.setCoincident(sq1.endPoint(), sq2.startPoint(), True)
        sketch.setCoincident(sq2.endPoint(), sq3.startPoint(), True)
        sketch.setCoincident(sq3.endPoint(), sq4.startPoint(), True)
        sketch.setHorizontal(sq4.result(), True)
        sketch.setPerpendicular(sq1.result(), sq2.result(), True)
        sketch.setPerpendicular(sq2.result(), sq3.result(), True)
        sketch.setPerpendicular(sq3.result(), sq4.result(), True)
        
        sketch.setLength(sq1.result(), math_side, True)
        sketch.setLength(sq2.result(), math_side, True)
        
        sketch.setCoincident(sq1.startPoint(), c1.startPoint(), True)
        sketch.setCoincident(sq3.startPoint(), c1.endPoint(), True)
        sketch.setCoincident(sq2.startPoint(), c2.startPoint(), True)
        sketch.setCoincident(sq4.startPoint(), c2.endPoint(), True)
        sketch.setCoincident(p0.coordinates(), c1.result(), True)
        sketch.setCoincident(p0.coordinates(), c2.result(), True)
        
        sketch.setCoincident(sq4.startPoint(), d1.startPoint(), True) 
        sketch.setCoincident(sq1.startPoint(), d2.startPoint(), True) 
        sketch.setCoincident(sq2.startPoint(), d3.startPoint(), True) 
        sketch.setCoincident(sq3.startPoint(), d4.startPoint(), True) 
        
        sketch.setAngle(d1.result(), sq4.result(), 135.0, type="Direct", is_active=True)
        sketch.setAngle(d2.result(), sq1.result(), 135.0, type="Direct", is_active=True)
        sketch.setAngle(d3.result(), sq2.result(), 135.0, type="Direct", is_active=True)
        sketch.setAngle(d4.result(), sq3.result(), 135.0, type="Direct", is_active=True)
        
        sketch.setLength(d1.result(), L_ext, True)
        sketch.setLength(d2.result(), L_ext, True)
        sketch.setLength(d3.result(), L_ext, True)
        sketch.setLength(d4.result(), L_ext, True)

        sketch.execute(True)
        try: self._preview_names.append(sketch.name())
        except: pass

        # =========================================================
        # 6. EXTRUSIÓN Y CORTE FINAL
        # =========================================================
        extrusion = model.addExtrusion(
            doc, 
            [sketch.result()], 
            model.selection(), 
            0.0, 
            H + 0.1, 
            "Edges"
        )
        extrusion.execute(True)
        try: self._preview_names.append(extrusion.name())
        except: pass

        split = model.addSplit(
            doc, 
            [model.selection("SOLID", solid_name)], 
            [extrusion.result()], 
            keepSubResults=False
        )
        split.execute(True)
        try: self._preview_names.append(split.name())
        except: pass
        
    def isMacro(self):
        return True