"""
骨骼动画系统插件 - 动画优化和压缩模块
提供动画压缩、优化、缓存等功能
"""

from panda3d.core import *
from direct.actor.Actor import Actor
import math
import json


class AnimationCompression:
    """
    动画压缩系统
    支持关键帧压缩、插值优化等技术
    """
    
    def __init__(self):
        self.compression_settings = {
            'position_threshold': 0.01,  # 位置阈值
            'rotation_threshold': 0.1,   # 旋转阈值（度）
            'scale_threshold': 0.01,     # 缩放阈值
            'enable_quantization': True, # 是否启用量化
            'quantization_bits': 16      # 量化位数
        }
        
    def compress_animation(self, actor, anim_name, output_path=None):
        """
        压缩指定动画
        :param actor: Actor对象
        :param anim_name: 动画名称
        :param output_path: 输出路径
        :return: 压缩后的动画数据
        """
        if anim_name not in actor.getAnimNames():
            print(f"错误: 动画 {anim_name} 不存在")
            return None
            
        print(f"开始压缩动画: {anim_name}")
        
        # 获取动画控制
        anim_control = actor.getAnimControl(anim_name)
        if not anim_control:
            print(f"错误: 无法获取动画控制 {anim_name}")
            return None
            
        # 获取动画数据
        num_frames = anim_control.getNumFrames()
        frame_rate = anim_control.getFrameRate()
        
        print(f"  动画信息: {num_frames} 帧, {frame_rate} FPS")
        
        # 压缩数据结构
        compressed_data = {
            'name': anim_name,
            'frame_rate': frame_rate,
            'duration': num_frames / frame_rate if frame_rate > 0 else 0,
            'channels': {}
        }
        
        # 获取骨骼列表
        channels = self._extract_animation_channels(actor, anim_name, num_frames)
        
        # 压缩每个通道
        for bone_name, channel_data in channels.items():
            compressed_channel = self._compress_channel(channel_data)
            compressed_data['channels'][bone_name] = compressed_channel
            
        # 保存压缩数据
        if output_path:
            self._save_compressed_animation(compressed_data, output_path)
            
        compression_ratio = self._calculate_compression_ratio(channels, compressed_data)
        print(f"  压缩完成，压缩比: {compression_ratio:.2f}:1")
        
        return compressed_data
        
    def _extract_animation_channels(self, actor, anim_name, num_frames):
        """
        提取动画通道数据
        :param actor: Actor对象
        :param anim_name: 动画名称
        :param num_frames: 帧数
        :return: 通道数据字典
        """
        channels = {}
        
        # 获取所有骨骼
        bundle = actor.getPartBundleDict().get('modelRoot')
        if not bundle:
            return channels
            
        # 遍历骨骼
        for i in range(bundle.getNumChildren()):
            child = bundle.getChild(i)
            if hasattr(child, 'getName'):
                bone_name = child.getName()
                channels[bone_name] = self._extract_bone_channel(actor, bone_name, anim_name, num_frames)
                
        return channels
        
    def _extract_bone_channel(self, actor, bone_name, anim_name, num_frames):
        """
        提取单个骨骼的动画通道
        :param actor: Actor对象
        :param bone_name: 骨骼名称
        :param anim_name: 动画名称
        :param num_frames: 帧数
        :return: 骨骼动画数据
        """
        channel_data = {
            'positions': [],
            'rotations': [],
            'scales': []
        }
        
        # 为每一帧提取数据
        for frame in range(num_frames):
            # 设置到指定帧
            actor.pose(anim_name, frame)
            
            # 获取骨骼变换
            joint = actor.exposeJoint(None, "modelRoot", bone_name)
            if joint:
                pos = joint.getPos()
                hpr = joint.getHpr()
                scale = joint.getScale()
                
                channel_data['positions'].append((pos.x, pos.y, pos.z))
                channel_data['rotations'].append((hpr.x, hpr.y, hpr.z))
                channel_data['scales'].append((scale.x, scale.y, scale.z))
                
        return channel_data
        
    def _compress_channel(self, channel_data):
        """
        压缩单个通道
        :param channel_data: 通道数据
        :return: 压缩后的通道数据
        """
        compressed_channel = {
            'positions': self._compress_keyframes(channel_data['positions'], 'position'),
            'rotations': self._compress_keyframes(channel_data['rotations'], 'rotation'),
            'scales': self._compress_keyframes(channel_data['scales'], 'scale')
        }
        
        return compressed_channel
        
    def _compress_keyframes(self, keyframes, data_type):
        """
        压缩关键帧数据
        :param keyframes: 关键帧数据
        :param data_type: 数据类型 ('position', 'rotation', 'scale')
        :return: 压缩后的关键帧
        """
        if not keyframes:
            return []
            
        # 根据数据类型获取阈值
        if data_type == 'position':
            threshold = self.compression_settings['position_threshold']
        elif data_type == 'rotation':
            threshold = self.compression_settings['rotation_threshold']
        else:  # scale
            threshold = self.compression_settings['scale_threshold']
            
        compressed_keyframes = []
        compressed_keyframes.append((0, keyframes[0]))  # 第一帧总是保留
        
        # 关键帧简化算法
        last_keyframe = keyframes[0]
        for i in range(1, len(keyframes)):
            current_keyframe = keyframes[i]
            
            # 检查是否需要保留这一帧
            if self._needs_keyframe(last_keyframe, current_keyframe, threshold, data_type):
                compressed_keyframes.append((i, current_keyframe))
                last_keyframe = current_keyframe
                
        # 总是保留最后一帧
        if len(keyframes) > 1 and (len(compressed_keyframes) == 0 or compressed_keyframes[-1][0] != len(keyframes) - 1):
            compressed_keyframes.append((len(keyframes) - 1, keyframes[-1]))
            
        # 应用量化
        if self.compression_settings['enable_quantization']:
            compressed_keyframes = self._quantize_keyframes(compressed_keyframes, data_type)
            
        return compressed_keyframes
        
    def _needs_keyframe(self, last_frame, current_frame, threshold, data_type):
        """
        判断是否需要保留关键帧
        :param last_frame: 上一帧数据
        :param current_frame: 当前帧数据
        :param threshold: 阈值
        :param data_type: 数据类型
        :return: 是否需要保留
        """
        if data_type == 'rotation':
            # 旋转使用角度差
            diff_x = abs(current_frame[0] - last_frame[0])
            diff_y = abs(current_frame[1] - last_frame[1])
            diff_z = abs(current_frame[2] - last_frame[2])
            return diff_x > threshold or diff_y > threshold or diff_z > threshold
        else:
            # 位置和缩放使用欧几里得距离
            diff_x = abs(current_frame[0] - last_frame[0])
            diff_y = abs(current_frame[1] - last_frame[1])
            diff_z = abs(current_frame[2] - last_frame[2])
            distance = math.sqrt(diff_x**2 + diff_y**2 + diff_z**2)
            return distance > threshold
            
    def _quantize_keyframes(self, keyframes, data_type):
        """
        量化关键帧数据
        :param keyframes: 关键帧数据
        :param data_type: 数据类型
        :return: 量化后的关键帧
        """
        bits = self.compression_settings['quantization_bits']
        max_value = 2**(bits - 1) - 1
        min_value = -2**(bits - 1)
        
        quantized_keyframes = []
        for frame_index, frame_data in keyframes:
            quantized_data = []
            for value in frame_data:
                # 简化量化过程
                quantized_value = max(min_value, min(max_value, int(value * (max_value / 10.0))))
                quantized_data.append(quantized_value)
            quantized_keyframes.append((frame_index, tuple(quantized_data)))
            
        return quantized_keyframes
        
    def _calculate_compression_ratio(self, original_data, compressed_data):
        """
        计算压缩比
        :param original_data: 原始数据
        :param compressed_data: 压缩数据
        :return: 压缩比
        """
        # 简化的压缩比计算
        original_size = sum(len(channel['positions']) + len(channel['rotations']) + len(channel['scales']) 
                           for channel in original_data.values())
        compressed_size = sum(len(channel['positions']) + len(channel['rotations']) + len(channel['scales']) 
                             for channel in compressed_data['channels'].values())
        
        if compressed_size == 0:
            return 1.0
            
        return original_size / compressed_size
        
    def _save_compressed_animation(self, compressed_data, output_path):
        """
        保存压缩动画数据
        :param compressed_data: 压缩数据
        :param output_path: 输出路径
        """
        try:
            with open(output_path, 'w') as f:
                json.dump(compressed_data, f, indent=2)
            print(f"压缩动画已保存到: {output_path}")
        except Exception as e:
            print(f"保存压缩动画失败: {e}")


class AnimationCaching:
    """
    动画缓存系统
    提供动画数据缓存以提高性能
    """
    
    def __init__(self, max_cache_size=100):
        self.max_cache_size = max_cache_size
        self.cache = {}  # {anim_key: anim_data}
        self.access_order = []  # 访问顺序，用于LRU
        self.cache_stats = {
            'hits': 0,
            'misses': 0,
            'evictions': 0
        }
        
    def get_cached_animation(self, actor_id, anim_name):
        """
        获取缓存的动画数据
        :param actor_id: Actor ID
        :param anim_name: 动画名称
        :return: 动画数据或None
        """
        cache_key = f"{actor_id}:{anim_name}"
        
        if cache_key in self.cache:
            # 缓存命中
            self.cache_stats['hits'] += 1
            
            # 更新访问顺序
            if cache_key in self.access_order:
                self.access_order.remove(cache_key)
            self.access_order.append(cache_key)
            
            print(f"动画缓存命中: {cache_key}")
            return self.cache[cache_key]
        else:
            # 缓存未命中
            self.cache_stats['misses'] += 1
            print(f"动画缓存未命中: {cache_key}")
            return None
            
    def cache_animation(self, actor_id, anim_name, anim_data):
        """
        缓存动画数据
        :param actor_id: Actor ID
        :param anim_name: 动画名称
        :param anim_data: 动画数据
        """
        cache_key = f"{actor_id}:{anim_name}"
        
        # 如果缓存已满，移除最久未使用的项
        if len(self.cache) >= self.max_cache_size:
            if self.access_order:
                oldest_key = self.access_order.pop(0)
                del self.cache[oldest_key]
                self.cache_stats['evictions'] += 1
                print(f"动画缓存已满，移除最旧项: {oldest_key}")
                
        # 添加新项
        self.cache[cache_key] = anim_data
        self.access_order.append(cache_key)
        
        print(f"动画已缓存: {cache_key}")
        
    def invalidate_cache(self, actor_id=None, anim_name=None):
        """
        使缓存失效
        :param actor_id: Actor ID，如果为None则匹配所有
        :param anim_name: 动画名称，如果为None则匹配所有
        """
        if actor_id is None and anim_name is None:
            # 清空所有缓存
            self.cache.clear()
            self.access_order.clear()
            print("所有动画缓存已清空")
            return
            
        # 构建匹配模式
        if actor_id and anim_name:
            pattern = f"{actor_id}:{anim_name}"
            keys_to_remove = [k for k in self.cache.keys() if k == pattern]
        elif actor_id:
            pattern = f"{actor_id}:"
            keys_to_remove = [k for k in self.cache.keys() if k.startswith(pattern)]
        else:  # anim_name only
            keys_to_remove = [k for k in self.cache.keys() if k.endswith(f":{anim_name}")]
            
        # 移除匹配项
        for key in keys_to_remove:
            if key in self.cache:
                del self.cache[key]
            if key in self.access_order:
                self.access_order.remove(key)
                
        print(f"已使 {len(keys_to_remove)} 项缓存失效")
        
    def get_cache_stats(self):
        """
        获取缓存统计信息
        :return: 统计信息字典
        """
        total_requests = self.cache_stats['hits'] + self.cache_stats['misses']
        hit_rate = self.cache_stats['hits'] / total_requests if total_requests > 0 else 0
        
        return {
            'cache_size': len(self.cache),
            'max_size': self.max_cache_size,
            'hits': self.cache_stats['hits'],
            'misses': self.cache_stats['misses'],
            'evictions': self.cache_stats['evictions'],
            'hit_rate': hit_rate,
            'total_requests': total_requests
        }
        
    def clear_stats(self):
        """
        清空统计信息
        """
        self.cache_stats = {
            'hits': 0,
            'misses': 0,
            'evictions': 0
        }


class AnimationLOD:
    """
    动画细节层次系统
    根据距离和重要性调整动画质量
    """
    
    def __init__(self, world):
        self.world = world
        self.lod_settings = {
            'distance_lod': True,
            'distance_thresholds': [10, 20, 50],  # 距离阈值
            'lod_levels': [0, 1, 2, 3],  # LOD级别
            'performance_lod': True,
            'fps_threshold': 30,  # FPS阈值
            'automatic_lod': True
        }
        self.actor_lod_levels = {}  # {actor_id: lod_level}
        self.performance_monitor = {
            'last_fps': 60,
            'frame_times': []
        }
        
    def update_lod_levels(self, camera_pos):
        """
        更新所有Actor的LOD级别
        :param camera_pos: 摄像机位置
        """
        if not self.lod_settings['automatic_lod']:
            return
            
        # 获取所有Actor
        actors = self.world.render.findAllMatches("**/+ActorNode")
        
        for actor_np in actors:
            actor = actor_np.node()
            if not isinstance(actor, Actor):
                continue
                
            actor_id = actor_np.getName()
            
            # 计算距离LOD
            distance_lod = 0
            if self.lod_settings['distance_lod']:
                distance = (actor_np.getPos() - camera_pos).length()
                thresholds = self.lod_settings['distance_thresholds']
                
                for i, threshold in enumerate(thresholds):
                    if distance > threshold:
                        distance_lod = i + 1
                        
            # 计算性能LOD
            performance_lod = 0
            if self.lod_settings['performance_lod']:
                if self.performance_monitor['last_fps'] < self.lod_settings['fps_threshold']:
                    performance_lod = 1
                    
            # 确定最终LOD级别
            final_lod = max(distance_lod, performance_lod)
            self.actor_lod_levels[actor_id] = final_lod
            
            # 应用LOD设置
            self._apply_lod_settings(actor, actor_id, final_lod)
            
    def _apply_lod_settings(self, actor, actor_id, lod_level):
        """
        应用LOD设置到Actor
        :param actor: Actor对象
        :param actor_id: Actor ID
        :param lod_level: LOD级别
        """
        # LOD级别说明:
        # 0 - 最高质量 (全帧率，全骨骼)
        # 1 - 中等质量 (半帧率，全骨骼)
        # 2 - 低质量 (1/4帧率，简化骨骼)
        # 3 - 最低质量 (1/10帧率，极简骨骼)
        
        if lod_level == 0:
            # 最高质量 - 不做调整
            pass
        elif lod_level == 1:
            # 中等质量 - 降低播放速度
            for anim_name in actor.getAnimNames():
                current_rate = actor.getPlayRate(anim_name)
                actor.setPlayRate(current_rate * 0.5, anim_name)
        elif lod_level == 2:
            # 低质量 - 进一步降低播放速度
            for anim_name in actor.getAnimNames():
                current_rate = actor.getPlayRate(anim_name)
                actor.setPlayRate(current_rate * 0.25, anim_name)
        elif lod_level == 3:
            # 最低质量 - 大幅降低播放速度
            for anim_name in actor.getAnimNames():
                current_rate = actor.getPlayRate(anim_name)
                actor.setPlayRate(current_rate * 0.1, anim_name)
                
        print(f"Actor {actor_id} LOD级别设置为: {lod_level}")
        
    def set_actor_importance(self, actor_id, importance):
        """
        设置Actor重要性（影响LOD计算）
        :param actor_id: Actor ID
        :param importance: 重要性 (0.0 - 1.0)
        """
        # 重要性高的Actor会获得更好的LOD级别
        # 这里可以实现更复杂的逻辑
        pass
        
    def update_performance_stats(self, current_fps, frame_time):
        """
        更新性能统计
        :param current_fps: 当前FPS
        :param frame_time: 帧时间
        """
        self.performance_monitor['last_fps'] = current_fps
        self.performance_monitor['frame_times'].append(frame_time)
        
        # 保持最近30帧的统计数据
        if len(self.performance_monitor['frame_times']) > 30:
            self.performance_monitor['frame_times'] = self.performance_monitor['frame_times'][-30:]
            
    def get_lod_stats(self):
        """
        获取LOD统计信息
        :return: 统计信息
        """
        lod_counts = {}
        for lod_level in self.actor_lod_levels.values():
            if lod_level not in lod_counts:
                lod_counts[lod_level] = 0
            lod_counts[lod_level] += 1
            
        return {
            'actor_lod_distribution': lod_counts,
            'current_fps': self.performance_monitor['last_fps'],
            'average_frame_time': sum(self.performance_monitor['frame_times']) / len(self.performance_monitor['frame_times']) 
                                 if self.performance_monitor['frame_times'] else 0
        }


# 使用示例
def example_compression_usage(actor):
    """
    动画压缩使用示例
    """
    print("=== 动画压缩使用示例 ===")
    
    compressor = AnimationCompression()
    
    # 压缩所有动画
    for anim_name in actor.getAnimNames():
        print(f"压缩动画: {anim_name}")
        compressed_data = compressor.compress_animation(actor, anim_name)
        if compressed_data:
            print(f"  压缩成功: {len(compressed_data['channels'])} 个骨骼通道")
            
    print("动画压缩示例完成")


def example_caching_usage(world):
    """
    动画缓存使用示例
    """
    print("=== 动画缓存使用示例 ===")
    
    cache = AnimationCaching(max_cache_size=50)
    
    # 模拟缓存一些动画数据
    dummy_data = {'frames': 100, 'channels': 20}
    cache.cache_animation("actor_1", "walk", dummy_data)
    cache.cache_animation("actor_1", "run", dummy_data)
    cache.cache_animation("actor_2", "idle", dummy_data)
    
    # 尝试获取缓存数据
    data = cache.get_cached_animation("actor_1", "walk")
    if data:
        print("  成功获取缓存数据")
        
    # 查看缓存统计
    stats = cache.get_cache_stats()
    print(f"  缓存统计: {stats}")
    
    print("动画缓存示例完成")


def example_lod_usage(world):
    """
    动画LOD使用示例
    """
    print("=== 动画LOD使用示例 ===")
    
    lod_system = AnimationLOD(world)
    
    # 模拟更新LOD级别
    camera_pos = Point3(0, 0, 0)
    lod_system.update_lod_levels(camera_pos)
    
    # 更新性能统计
    lod_system.update_performance_stats(45, 0.022)  # 45 FPS, 22ms per frame
    
    # 获取LOD统计
    stats = lod_system.get_lod_stats()
    print(f"  LOD统计: {stats}")
    
    print("动画LOD示例完成")


if __name__ == "__main__":
    print("动画优化和压缩模块加载完成")