# -*- coding: utf-8 -*-
"""
rhino_builder.py - Rhino 几何生成器

在 Rhino 中生成三维实体模型
需要在 Rhino Python 环境中运行

使用 rhinoscriptsyntax 实现，简单易用
"""

from typing import List, Optional, Tuple
import math

try:
    import rhinoscriptsyntax as rs
    RHINO_AVAILABLE = True
except ImportError:
    RHINO_AVAILABLE = False
    print("警告: 此模块需要在 Rhino Python 环境中运行")

from PySap2000.geometry.element_geometry import Model3D, FrameElement3D, CableElement3D
from PySap2000.geometry.section_profile import create_profile_from_sap_section, rotate_profile_points


class RhinoBuilder:
    """Rhino 几何生成器（使用 rhinoscriptsyntax）"""
    
    def __init__(self):
        if not RHINO_AVAILABLE:
            raise ImportError("此模块需要在 Rhino Python 环境中运行")
        
        self.created_guids = []  # 记录创建的对象
        self.layer_structure = {}  # 图层结构
    
    def _print_progress(self, current: int, total: int, prefix: str = ""):
        """打印进度条（Rhino 控制台优化版）"""
        if total == 0:
            return
        
        percent = int(100 * current / total)
        
        # 使用实例变量跟踪上次打印的百分比
        if not hasattr(self, '_last_printed_percent'):
            self._last_printed_percent = -1
        
        # 只在百分比变化且是 10 的倍数时打印，或者是最后一个
        if (percent != self._last_printed_percent and percent % 10 == 0) or current == total:
            bar_length = 30
            filled = int(bar_length * current / total)
            bar = '█' * filled + '░' * (bar_length - filled)
            
            # 计算 total 的位数，用于固定宽度
            total_width = len(str(total))
            
            # 固定宽度格式化
            count_str = f"({current:>{total_width}}/{total})"
            progress_text = f"{prefix}[{bar}] {percent:3d}% {count_str}"
            
            print(progress_text)
            self._last_printed_percent = percent
    
    def build_model(
        self, 
        model_3d: Model3D, 
        layer_name: str = "SAP2000_Model",
        num_segments: int = 8,
        organize_by_group: bool = True,
        organize_by_type: bool = True,
        create_solid: bool = True,
        num_threads: int = 4
    ) -> dict:
        """
        在 Rhino 中生成三维模型
        
        Args:
            model_3d: Model3D 对象
            layer_name: 根图层名称
            num_segments: 圆形截面分段数
            organize_by_group: 是否按组创建子图层
            organize_by_type: 是否按类型创建子图层
            create_solid: 是否创建实体（False 则创建线框）
            num_threads: 线程数（默认4，仅实体模式有效）
            
        Returns:
            结果字典 {
                "guids": [生成的对象 GUID],
                "layers": [创建的图层],
                "stats": {统计信息}
            }
            
        Example:
            # 在 Rhino Python 中运行
            from PySap2000.geometry import ModelExtractor
            from PySap2000.visualization.rhino import RhinoBuilder
            
            extractor = ModelExtractor(sap_model)
            model_3d = extractor.extract_all_elements()
            
            builder = RhinoBuilder()
            result = builder.build_model(model_3d, num_threads=4)
            print(f"创建了 {len(result['guids'])} 个对象")
        """
        print("=" * 60)
        print(f"单元数量: {len(model_3d.elements)}")
        print(f"生成模式: {'实体' if create_solid else '线框'}")
        print("=" * 60)
        
        # 禁用视图刷新以提升性能
        rs.EnableRedraw(False)
        
        self.created_guids = []
        self.layer_structure = {}
        
        # 创建根图层
        self._ensure_layer(layer_name)
        
        # 统计信息
        stats = {
            "total": len(model_3d.elements),
            "frame": 0,
            "cable": 0,
            "other": 0,
            "success": 0,
            "failed": 0
        }
        
        # 进度跟踪
        total_elements = len(model_3d.elements)
        
        # 按单元类型和组分类
        for idx, elem in enumerate(model_3d.elements):
            # 更新进度条
            self._print_progress(idx + 1, total_elements, "Progress: ")
            
            try:
                # 确定图层路径
                layer_path = self._get_layer_path(
                    elem, 
                    layer_name, 
                    organize_by_group, 
                    organize_by_type
                )
                self._ensure_layer(layer_path)
                rs.CurrentLayer(layer_path)
                
                # 生成几何体
                if isinstance(elem, FrameElement3D):
                    stats["frame"] += 1
                    if create_solid:
                        guid = self._build_frame_solid(elem, num_segments)
                    else:
                        guid = self._build_frame_line(elem)
                        
                elif isinstance(elem, CableElement3D):
                    stats["cable"] += 1
                    if create_solid:
                        guid = self._build_cable_solid(elem, num_segments)
                    else:
                        guid = self._build_cable_line(elem)
                else:
                    stats["other"] += 1
                    guid = self._build_line(elem)
                
                if guid:
                    self.created_guids.append(guid)
                    # 设置对象名称
                    rs.ObjectName(guid, elem.name)
                    
                    # 设置颜色：框架单元=蓝色，索单元=绿色
                    if isinstance(elem, FrameElement3D):
                        rs.ObjectColor(guid, (0, 0, 255))  # 蓝色
                    elif isinstance(elem, CableElement3D):
                        rs.ObjectColor(guid, (0, 255, 0))  # 绿色
                    
                    stats["success"] += 1
                else:
                    stats["failed"] += 1
                    print(f"\n  ⚠ 单元 '{elem.name}' 生成失败")
                    
            except Exception as e:
                stats["failed"] += 1
                print(f"\n  ✗ 单元 '{elem.name}' 异常: {e}")
                continue
        
        # 输出统计
        print("\n" + "=" * 60)
        print("生成完成!")
        print(f"  总计: {stats['total']} 个单元")
        print(f"  框架: {stats['frame']} | 索: {stats['cable']} | 其他: {stats['other']}")
        print(f"  成功: {stats['success']} | 失败: {stats['failed']}")
        print(f"  图层: {len(self.layer_structure)} 个")
        print("=" * 60)
        
        # 重新启用视图刷新
        rs.EnableRedraw(True)
        
        # 缩放到全部可见
        rs.ZoomExtents()
        
        return {
            "guids": self.created_guids,
            "layers": list(self.layer_structure.keys()),
            "stats": stats
        }
    
    def _get_layer_path(
        self, 
        elem, 
        root_layer: str, 
        by_group: bool, 
        by_type: bool
    ) -> str:
        """获取单元的图层路径"""
        parts = [root_layer]
        
        # 按组分类
        if by_group and elem.group:
            parts.append(elem.group)
        
        # 按类型分类
        if by_type:
            if isinstance(elem, FrameElement3D):
                parts.append("框架单元")
            elif isinstance(elem, CableElement3D):
                parts.append("索单元")
            else:
                parts.append("其他")
        
        return "::".join(parts)
    
    def _ensure_layer(self, layer_path: str):
        """确保图层存在（支持嵌套图层）"""
        if layer_path in self.layer_structure:
            return
        
        if not rs.IsLayer(layer_path):
            rs.AddLayer(layer_path)
        
        self.layer_structure[layer_path] = True
    
    def _build_frame_solid(self, elem: FrameElement3D, num_segments: int):
        """生成框架单元实体（使用 rhinoscriptsyntax）"""
        # 创建截面轮廓
        profile = create_profile_from_sap_section(elem.section_type, elem.section_params)
        
        # SD 截面：多子形状处理
        from PySap2000.geometry.section_profile import SDProfile, NonPrismaticProfile
        if isinstance(profile, SDProfile) and profile.shapes:
            return self._build_sd_frame_solid(elem, profile, num_segments)
        
        # 变截面：渐变拉伸
        if isinstance(profile, NonPrismaticProfile):
            return self._build_nonprismatic_frame_solid(elem, profile, num_segments)
        
        profile_points_2d = profile.get_profile_points(num_segments)
        
        # 应用局部坐标轴旋转角度
        if elem.local_axis_angle:
            profile_points_2d = rotate_profile_points(profile_points_2d, elem.local_axis_angle)
        
        # 单元起点和终点
        start_pt = [elem.point_i.x, elem.point_i.y, elem.point_i.z]
        end_pt = [elem.point_j.x, elem.point_j.y, elem.point_j.z]
        
        # 计算局部坐标系
        direction, local_y, local_z = self._compute_local_axes(start_pt, end_pt)
        
        # 拉伸单个轮廓
        return self._extrude_profile_along_axis(
            profile_points_2d, None, start_pt, end_pt, local_y, local_z
        )
    
    def _build_sd_frame_solid(self, elem: FrameElement3D, profile, num_segments: int):
        """
        生成 SD 截面框架单元实体
        
        SD 截面包含多个子形状，每个子形状单独拉伸，
        空心截面通过布尔差集实现。最终合并为一个实体。
        """
        start_pt = [elem.point_i.x, elem.point_i.y, elem.point_i.z]
        end_pt = [elem.point_j.x, elem.point_j.y, elem.point_j.z]
        
        direction, local_y, local_z = self._compute_local_axes(start_pt, end_pt)
        
        solids = []
        for shape_data in profile.get_all_shapes():
            outer_pts = shape_data.points
            inner_pts = shape_data.inner_points
            
            # 应用局部坐标轴旋转
            if elem.local_axis_angle:
                outer_pts = rotate_profile_points(outer_pts, elem.local_axis_angle)
                if inner_pts:
                    inner_pts = rotate_profile_points(inner_pts, elem.local_axis_angle)
            
            solid = self._extrude_profile_along_axis(
                outer_pts, inner_pts, start_pt, end_pt, local_y, local_z
            )
            if solid:
                solids.append(solid)
        
        if not solids:
            return None
        
        # 如果只有一个实体，直接返回
        if len(solids) == 1:
            return solids[0]
        
        # 多个实体合并为组
        try:
            group = rs.AddGroup()
            rs.AddObjectsToGroup(solids, group)
            return solids[0]  # 返回第一个作为主对象
        except Exception:
            return solids[0]
    
    def _build_nonprismatic_frame_solid(
        self, elem: FrameElement3D, profile, num_segments: int
    ):
        """
        生成变截面框架单元实体
        
        通过在起点和终点分别创建截面轮廓曲线，然后使用 Loft 生成渐变实体。
        """
        start_pts_2d = profile.get_start_profile_points(num_segments)
        end_pts_2d = profile.get_end_profile_points(num_segments)
        
        if not start_pts_2d or not end_pts_2d:
            return None
        
        # 确保起点和终点轮廓点数一致（Loft 要求）
        # 如果点数不同，对少的那个进行插值补点
        if len(start_pts_2d) != len(end_pts_2d):
            target_count = max(len(start_pts_2d), len(end_pts_2d))
            start_pts_2d = self._resample_profile(start_pts_2d, target_count)
            end_pts_2d = self._resample_profile(end_pts_2d, target_count)
        
        # 应用局部坐标轴旋转
        if elem.local_axis_angle:
            start_pts_2d = rotate_profile_points(start_pts_2d, elem.local_axis_angle)
            end_pts_2d = rotate_profile_points(end_pts_2d, elem.local_axis_angle)
        
        start_pt = [elem.point_i.x, elem.point_i.y, elem.point_i.z]
        end_pt = [elem.point_j.x, elem.point_j.y, elem.point_j.z]
        direction, local_y, local_z = self._compute_local_axes(start_pt, end_pt)
        
        # 起点截面三维轮廓
        start_3d = self._profile_2d_to_3d(start_pts_2d, start_pt, local_y, local_z)
        start_3d.append(start_3d[0])
        
        # 终点截面三维轮廓
        end_3d = self._profile_2d_to_3d(end_pts_2d, end_pt, local_y, local_z)
        end_3d.append(end_3d[0])
        
        # 创建轮廓曲线
        curve_start = rs.AddPolyline(start_3d)
        curve_end = rs.AddPolyline(end_3d)
        
        if not curve_start or not curve_end:
            if curve_start:
                rs.DeleteObject(curve_start)
            if curve_end:
                rs.DeleteObject(curve_end)
            return None
        
        # Loft 生成渐变实体
        try:
            solid = rs.AddLoftSrf([curve_start, curve_end])
            if solid and isinstance(solid, list):
                solid = solid[0]
        except Exception:
            solid = None
        
        # 如果 Loft 失败，回退到起点截面拉伸
        if not solid:
            path = rs.AddLine(start_pt, end_pt)
            if path:
                solid = rs.ExtrudeCurve(curve_start, path)
                rs.DeleteObject(path)
        
        rs.DeleteObject(curve_start)
        rs.DeleteObject(curve_end)
        
        return solid
    
    @staticmethod
    def _resample_profile(
        points: list, target_count: int
    ) -> list:
        """
        对轮廓点进行重采样，使点数达到 target_count
        
        通过沿轮廓线等距插值实现。
        """
        import math
        n = len(points)
        if n == target_count:
            return points
        if n == 0:
            return []
        
        # 计算累积弧长
        cum_len = [0.0]
        for i in range(1, n):
            dx = points[i][0] - points[i-1][0]
            dy = points[i][1] - points[i-1][1]
            cum_len.append(cum_len[-1] + math.sqrt(dx*dx + dy*dy))
        
        total_len = cum_len[-1]
        if total_len < 1e-10:
            return [points[0]] * target_count
        
        # 等距采样
        result = []
        for j in range(target_count):
            t = j / target_count * total_len
            # 找到 t 所在的线段
            for i in range(1, n):
                if cum_len[i] >= t:
                    seg_len = cum_len[i] - cum_len[i-1]
                    if seg_len < 1e-10:
                        result.append(points[i])
                    else:
                        ratio = (t - cum_len[i-1]) / seg_len
                        y = points[i-1][0] + ratio * (points[i][0] - points[i-1][0])
                        z = points[i-1][1] + ratio * (points[i][1] - points[i-1][1])
                        result.append((y, z))
                    break
            else:
                result.append(points[-1])
        
        return result

    def _compute_local_axes(self, start_pt, end_pt):
        """计算单元局部坐标系"""
        direction = rs.VectorCreate(end_pt, start_pt)
        direction = rs.VectorUnitize(direction)
        
        if abs(direction[2]) < 0.9:
            local_y = rs.VectorCrossProduct(direction, [0, 0, 1])
        else:
            local_y = rs.VectorCrossProduct(direction, [1, 0, 0])
        local_y = rs.VectorUnitize(local_y)
        
        local_z = rs.VectorCrossProduct(direction, local_y)
        local_z = rs.VectorUnitize(local_z)
        
        return direction, local_y, local_z
    
    def _extrude_profile_along_axis(
        self, outer_pts, inner_pts, start_pt, end_pt, local_y, local_z
    ):
        """
        沿轴线拉伸截面轮廓生成实体
        
        Args:
            outer_pts: 外轮廓二维点
            inner_pts: 内轮廓二维点（空心截面），None 表示实心
            start_pt: 起点三维坐标
            end_pt: 终点三维坐标
            local_y: 局部 Y 轴方向
            local_z: 局部 Z 轴方向
            
        Returns:
            Rhino 对象 GUID，失败返回 None
        """
        if not outer_pts:
            return None
        
        # 外轮廓转三维
        outer_3d = self._profile_2d_to_3d(outer_pts, start_pt, local_y, local_z)
        outer_3d.append(outer_3d[0])  # 闭合
        
        outer_curve = rs.AddPolyline(outer_3d)
        if not outer_curve:
            return None
        
        path = rs.AddLine(start_pt, end_pt)
        if not path:
            rs.DeleteObject(outer_curve)
            return None
        
        solid = rs.ExtrudeCurve(outer_curve, path)
        rs.DeleteObject(outer_curve)
        
        # 空心截面：创建内轮廓并做布尔差集
        if inner_pts and solid:
            inner_3d = self._profile_2d_to_3d(inner_pts, start_pt, local_y, local_z)
            inner_3d.append(inner_3d[0])
            
            inner_curve = rs.AddPolyline(inner_3d)
            if inner_curve:
                inner_solid = rs.ExtrudeCurve(inner_curve, path)
                rs.DeleteObject(inner_curve)
                
                if inner_solid:
                    try:
                        result = rs.BooleanDifference(solid, inner_solid, delete_input=True)
                        if result:
                            solid = result[0] if isinstance(result, list) else result
                    except Exception:
                        # 布尔运算失败，保留外轮廓实体
                        try:
                            rs.DeleteObject(inner_solid)
                        except Exception:
                            pass
        
        rs.DeleteObject(path)
        return solid
    
    def _profile_2d_to_3d(self, points_2d, origin, local_y, local_z):
        """将二维轮廓点转换为三维空间点"""
        points_3d = []
        for y, z in points_2d:
            pt = [
                origin[0] + local_y[0] * y + local_z[0] * z,
                origin[1] + local_y[1] * y + local_z[1] * z,
                origin[2] + local_y[2] * y + local_z[2] * z
            ]
            points_3d.append(pt)
        return points_3d
    
    def _build_frame_line(self, elem: FrameElement3D):
        """生成框架单元线框"""
        start_pt = [elem.point_i.x, elem.point_i.y, elem.point_i.z]
        end_pt = [elem.point_j.x, elem.point_j.y, elem.point_j.z]
        return rs.AddLine(start_pt, end_pt)
    
    def _build_cable_solid(self, elem: CableElement3D, num_segments: int):
        """生成索单元实体（圆柱体）"""
        profile = create_profile_from_sap_section(
            elem.section_type, 
            elem.section_params
        )
        profile_points_2d = profile.get_profile_points(num_segments)
        
        start_pt = [elem.point_i.x, elem.point_i.y, elem.point_i.z]
        end_pt = [elem.point_j.x, elem.point_j.y, elem.point_j.z]
        
        direction, local_y, local_z = self._compute_local_axes(start_pt, end_pt)
        
        return self._extrude_profile_along_axis(
            profile_points_2d, None, start_pt, end_pt, local_y, local_z
        )
    
    def _build_cable_line(self, elem: CableElement3D):
        """生成索单元线框"""
        start_pt = [elem.point_i.x, elem.point_i.y, elem.point_i.z]
        end_pt = [elem.point_j.x, elem.point_j.y, elem.point_j.z]
        return rs.AddLine(start_pt, end_pt)
    
    def _build_line(self, elem):
        """生成线（默认）"""
        start_pt = [elem.point_i.x, elem.point_i.y, elem.point_i.z]
        end_pt = [elem.point_j.x, elem.point_j.y, elem.point_j.z]
        return rs.AddLine(start_pt, end_pt)
    
    def select_created_objects(self):
        """选中所有创建的对象"""
        if self.created_guids:
            rs.SelectObjects(self.created_guids)
            print(f"已选中 {len(self.created_guids)} 个对象")
    
    def delete_created_objects(self):
        """删除所有创建的对象"""
        if self.created_guids:
            rs.DeleteObjects(self.created_guids)
            count = len(self.created_guids)
            self.created_guids = []
            print(f"已删除 {count} 个对象")
    
    def export_to_file(self, filepath: str, file_type: str = "3dm"):
        """
        导出创建的对象到文件
        
        Args:
            filepath: 输出文件路径
            file_type: 文件类型（3dm, obj, stl, step 等）
        """
        if not self.created_guids:
            print("没有对象可导出")
            return False
        
        # 选中要导出的对象
        rs.UnselectAllObjects()
        rs.SelectObjects(self.created_guids)
        
        # 导出
        try:
            if file_type.lower() == "3dm":
                rs.Command(f"_-Export {filepath} _Enter", False)
            elif file_type.lower() == "obj":
                rs.Command(f"_-Export {filepath} _Enter", False)
            elif file_type.lower() == "stl":
                rs.Command(f"_-Export {filepath} _Enter", False)
            else:
                print(f"不支持的文件类型: {file_type}")
                return False
            
            print(f"✓ 已导出到: {filepath}")
            return True
            
        except Exception as e:
            print(f"导出失败: {e}")
            return False
