from abc import ABC, abstractmethod
from collections import deque
from typing import List
from panda3d.core import NodePath, Point3


class Command(ABC):
    """
    抽象命令类，所有具体命令都需要继承此类
    """

    @abstractmethod
    def execute(self):
        """
        执行命令
        """
        pass

    @abstractmethod
    def undo(self):
        """
        撤销命令
        """
        pass

    @abstractmethod
    def redo(self):
        """
        重做命令
        """
        pass


class CommandManager:
    """
    命令管理器，负责管理命令的执行、撤销和重做
    """

    def __init__(self, max_history: int = 100):
        # 用于存储已执行的命令的历史记录
        self._undo_stack: deque = deque(maxlen=max_history)
        # 用于存储已撤销的命令，支持重做
        self._redo_stack: deque = deque(maxlen=max_history)
        # 最大历史记录数
        self._max_history = max_history

    def execute_command(self, command: Command):
        """
        执行命令，并将其添加到撤销栈中
        """
        try:
            command.execute()
            self._undo_stack.append(command)
            # 清空重做栈，因为执行新命令后就无法重做之前的命令了
            self._redo_stack.clear()
        except Exception as e:
            print(f"执行命令时出错: {e}")
            raise

    def undo(self) -> bool:
        """
        撤销上一个命令
        返回是否成功撤销
        """
        if not self._undo_stack:
            return False

        try:
            command = self._undo_stack.pop()
            command.undo()
            self._redo_stack.append(command)
            return True
        except Exception as e:
            print(f"撤销命令时出错: {e}")
            # 如果撤销失败，将命令放回撤销栈
            self._undo_stack.append(command)
            return False

    def redo(self) -> bool:
        """
        重做上一个被撤销的命令
        返回是否成功重做
        """
        if not self._redo_stack:
            return False

        try:
            command = self._redo_stack.pop()
            command.redo()
            self._undo_stack.append(command)
            return True
        except Exception as e:
            print(f"重做命令时出错: {e}")
            # 如果重做失败，将命令放回重做栈
            self._redo_stack.append(command)
            return False

    def can_undo(self) -> bool:
        """
        检查是否可以撤销
        """
        return len(self._undo_stack) > 0

    def can_redo(self) -> bool:
        """
        检查是否可以重做
        """
        return len(self._redo_stack) > 0

    def clear_history(self):
        """
        清空所有历史记录
        """
        self._undo_stack.clear()
        self._redo_stack.clear()

    def get_undo_count(self) -> int:
        """
        获取可撤销的命令数量
        """
        return len(self._undo_stack)

    def get_redo_count(self) -> int:
        """
        获取可重做的命令数量
        """
        return len(self._redo_stack)


# 示例命令实现
class MoveNodeCommand(Command):
    """
    移动节点命令示例
    """

    def __init__(self, node: NodePath, old_pos, new_pos):
        self.node = node
        self.old_pos = old_pos
        self.new_pos = new_pos

    def execute(self):
        """
        执行移动操作
        """
        self.node.setPos(self.new_pos)

    def undo(self):
        """
        撤销移动操作
        """
        self.node.setPos(self.old_pos)

    def redo(self):
        """
        重做移动操作
        """
        self.node.setPos(self.new_pos)


class DeleteNodeCommand(Command):
    """
    删除节点命令示例
    """

    def __init__(self, node: NodePath, parent_node: NodePath,world=None):
        self.node = node
        self.parent_node = parent_node
        self.world = world

        self.node_name = node.getName()
        self.node_pos = node.getPos()
        self.node_hpr = node.getHpr()
        self.node_scale = node.getScale()

        # 保存节点类型信息
        self.node_type = "NODE"
        if node.hasTag("tree_item_type"):
            self.node_type = node.getTag("tree_item_type")
        elif node.hasTag("gui_type"):
            gui_type = node.getTag("gui_type")
            if gui_type == "button":
                self.node_type = "GUI_BUTTON"
            elif gui_type == "label":
                self.node_type = "GUI_LABEL"
            elif gui_type == "entry":
                self.node_type = "GUI_ENTRY"
            elif gui_type == "2d_image":
                self.node_type = "GUI_IMAGE"
            elif gui_type == "3d_text":
                self.node_type = "GUI_3DTEXT"
            elif gui_type == "3d_image":
                self.node_type = "GUI_3DIMAGE"
            elif gui_type == "video_screen":
                self.node_type = "GUI_VIDEO_SCREEN"
            elif gui_type == "2d_video_screen":
                self.node_type = "GUI_2D_VIDEO_SCREEN"
        elif node.hasTag("light_type"):
            self.node_type = "LIGHT_NODE"
        elif node.hasTag("element_type") and node.getTag("element_type") == "cesium_tileset":
            self.node_type = "CESIUM_TILESET_NODE"
        elif node.hasTag("is_scene_element"):
            self.node_type = "SCENE_NODE"

        self.node_tags = {}
        if hasattr(node,'hasTag'):
            for tag_key in node.getNetTag('tags').split(',') if node.hasTag('tags') else []:
                if node.hasTag(tag_key):
                    self.node_tags[tag_key] = node.getTag(tag_key)
        else:
            try:
                if hasattr(node,'getTag'):
                    common_tags = ['is_scene_element','tree_item_type','gui_type','light_type',
                                   'element_type','file','model_path','video_path','image_path',
                                   'gui_text','name','created_by_user']
                    for tag in common_tags:
                        if node.hasTag(tag):
                            self.node_tags[tag] = node.getTag(tag)
            except:
                pass

        self.node_python_tags = {}
        if hasattr(node,'getPythonTagKeys'):
            try:
                for tag_key in node.getPythonTagKeys():
                    self.node_python_tags[tag_key] = node.getPythonTag(tag_key)
            except Exception as e:
                pass

        # 对于特定类型的节点，保存额外的数据
        self.extra_data = {}
        if self.node_type in ["GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_IMAGE",
                              "GUI_3DTEXT", "GUI_3DIMAGE", "GUI_VIDEO_SCREEN", "GUI_2D_VIDEO_SCREEN"]:
            if node.hasTag("gui_text"):
                self.extra_data["gui_text"] = node.getTag("gui_text")
            if node.hasTag("video_path"):
                self.extra_data["video_path"] = node.getTag("video_path")
            if node.hasTag("image_path"):
                self.extra_data["image_path"] = node.getTag("image_path")
        elif self.node_type == "LIGHT_NODE":
            if node.hasTag("light_type"):
                self.extra_data["light_type"] = node.getTag("light_type")
            rp_light = node.getPythonTag("rp_light_object")
            if rp_light:
                self.extra_data["light_data"] = {
                    'energy': getattr(rp_light, 'energy', 5000),
                    'radius': getattr(rp_light, 'radius', 1000),
                    'fov': getattr(rp_light, 'fov', 70) if hasattr(rp_light, 'fov') else 70,
                    'inner_radius': getattr(rp_light, 'inner_radius', 0.4) if hasattr(rp_light,
                                                                                      'inner_radius') else 0.4,
                    'casts_shadows': getattr(rp_light, 'casts_shadows', True),
                    'shadow_map_resolution': getattr(rp_light, 'shadow_map_resolution', 256)
                }
        elif self.node_type == "CESIUM_TILESET_NODE":
            if node.hasTag("tileset_url"):
                self.extra_data["tileset_url"] = node.getTag("tileset_url")

    def execute(self):
        """
        执行删除操作
        """
        # 从world的相应列表中移除节点引用
        if self.world and hasattr(self.world, 'scene_manager'):
            scene_manager = self.world.scene_manager
            if self.node_type == "LIGHT_NODE":
                if self.node.hasTag("light_type"):
                    light_type = self.node.getTag("light_type")
                    if light_type == "spot_light" and hasattr(scene_manager,
                                                              'Spotlight') and self.node in scene_manager.Spotlight:
                        scene_manager.Spotlight.remove(self.node)
                    elif light_type == "point_light" and hasattr(scene_manager,
                                                                 'Pointlight') and self.node in scene_manager.Pointlight:
                        scene_manager.Pointlight.remove(self.node)
            elif self.node_type == "IMPORTED_MODEL_NODE" and hasattr(scene_manager,
                                                                     'models') and self.node in scene_manager.models:
                scene_manager.models.remove(self.node)
            elif self.node_type.startswith("GUI_") and hasattr(self.world,
                                                               'gui_elements') and self.node in self.world.gui_elements:
                self.world.gui_elements.remove(self.node)
            elif self.node_type == "CESIUM_TILESET_NODE":
                # 从tilesets列表中移除
                if hasattr(scene_manager, 'tilesets'):
                    tilesets_to_remove = []
                    for i, tileset_info in enumerate(scene_manager.tilesets):
                        if tileset_info.get('node') == self.node:
                            tilesets_to_remove.append(i)
                    for i in reversed(tilesets_to_remove):
                        del scene_manager.tilesets[i]

        # 从场景图中移除节点
        if self.node and not self.node.isEmpty():
            self.node.removeNode()

    def undo(self):
        """
        撤销删除操作（重新创建节点）
        """
        try:
            # 使用场景管理器重建节点
            if self.world and hasattr(self.world, 'scene_manager'):
                scene_manager = self.world.scene_manager

                # 创建节点数据字典
                node_data = {
                    'name': self.node_name,
                    'node_type': self.node_type,
                    'pos': (self.node_pos.x, self.node_pos.y, self.node_pos.z),
                    'hpr': (self.node_hpr.x, self.node_hpr.y, self.node_hpr.z),
                    'scale': (self.node_scale.x, self.node_scale.y, self.node_scale.z),
                    'tags': self.node_tags
                }

                # 添加额外数据
                if self.extra_data:
                    if self.node_type.startswith("GUI_"):
                        node_data['gui_data'] = self.extra_data
                    elif self.node_type == "LIGHT_NODE":
                        node_data['light_data'] = self.extra_data.get('light_data', {})
                    elif self.node_type == "CESIUM_TILESET_NODE":
                        node_data['tileset_url'] = self.extra_data.get('tileset_url', '')

                # 重建节点
                new_node = scene_manager.recreateNodeFromData(node_data, self.parent_node)

                if new_node:
                    print(f"✅ 成功撤销删除操作，节点 {self.node_name} 已恢复")
                    # 更新节点引用
                    self.node = new_node
                else:
                    print(f"❌ 撤销删除操作失败，无法重建节点 {self.node_name}")
            else:
                print("❌ 无法撤销删除操作，缺少场景管理器引用")

        except Exception as e:
            print(f"❌ 撤销删除操作时出错: {e}")
            import traceback
            traceback.print_exc()

    def redo(self):
        """
        重做删除操作
        """
        self.execute()


class RotateNodeCommand(Command):
    """
    旋转节点命令
    """

    def __init__(self, node: NodePath, old_hpr, new_hpr):
        self.node = node
        self.old_hpr = old_hpr
        self.new_hpr = new_hpr

    def execute(self):
        """
        执行旋转操作
        """
        self.node.setHpr(self.new_hpr)

    def undo(self):
        """
        撤销旋转操作
        """
        self.node.setHpr(self.old_hpr)

    def redo(self):
        """
        重做旋转操作
        """
        self.node.setHpr(self.new_hpr)


class ScaleNodeCommand(Command):
    """
    缩放节点命令
    """

    def __init__(self, node: NodePath, old_scale, new_scale):
        self.node = node
        self.old_scale = old_scale
        self.new_scale = new_scale

    def execute(self):
        """
        执行缩放操作
        """
        self.node.setScale(self.new_scale)

    def undo(self):
        """
        撤销缩放操作
        """
        self.node.setScale(self.old_scale)

    def redo(self):
        """
        重做缩放操作
        """
        self.node.setScale(self.new_scale)


class CreateNodeCommand(Command):
    """
    创建节点命令
    """

    def __init__(self, node_creator_func,parent_node, *args, **kwargs):
        self.node_creator_func = node_creator_func
        self.parent_node = parent_node
        self.args = args
        self.kwargs = kwargs
        self.created_node = None

    def execute(self):
        """
        执行创建节点操作
        """
        self.created_node = self.node_creator_func(self.parent_node,*self.args, **self.kwargs)
        return self.created_node

    def undo(self):
        """
        撤销创建节点操作
        """
        if self.created_node:
            self.created_node.removeNode()

    def redo(self):
        """
        重做创建节点操作
        """
        self.execute()


class ReparentNodeCommand(Command):
    """
    重新设置节点父子关系命令 - 增强版（同时处理Panda3D和Qt树）
    """

    def __init__(self, node: NodePath, old_parent: NodePath, new_parent: NodePath,
                 old_parent_item=None, new_parent_item=None, is_2d_gui=False, world=None):
        self.node = node
        self.old_parent = old_parent
        self.new_parent = new_parent
        self.old_parent_item = old_parent_item  # Qt树中的旧父节点项
        self.new_parent_item = new_parent_item  # Qt树中的新父节点项
        self.is_2d_gui = is_2d_gui
        self.world = world

        # 保存节点在操作前的世界坐标和局部坐标，以便在撤销/重做时保持位置不变
        self.world_pos = node.getPos(self.world.render if self.world else node.getParent())
        self.world_hpr = node.getHpr(self.world.render if self.world else node.getParent())
        self.world_scale = node.getScale(self.world.render if self.world else node.getParent())
        # 同时保存局部坐标，因为在父节点改变后可能需要恢复
        self.local_pos = node.getPos()
        self.local_hpr = node.getHpr()
        self.local_scale = node.getScale()

    def _updateQtTree(self, node_item, new_parent_item):
        """更新Qt树控件中的节点位置"""
        if not node_item or not new_parent_item:
            return

        # 从当前父节点中移除
        current_parent = node_item.parent()
        if current_parent:
            current_parent.removeChild(node_item)
        else:
            # 如果是顶级项目
            tree_widget = node_item.treeWidget()
            if tree_widget:
                index = tree_widget.indexOfTopLevelItem(node_item)
                if index >= 0:
                    tree_widget.takeTopLevelItem(index)

        # 添加到新父节点
        new_parent_item.addChild(node_item)

    def execute(self):
        """
        执行重新父化操作
        """
        # 更新Panda3D节点父子关系
        if self.is_2d_gui and self.world:
            # 2D GUI元素需要特殊处理
            if self.new_parent and not self.new_parent.isEmpty():
                if hasattr(self.new_parent, 'getTag') and self.new_parent.getTag("is_gui_element") == "1":
                    # 目标是GUI元素，直接重新父化
                    self.node.wrtReparentTo(self.new_parent)
                else:
                    # 目标是3D节点，保持GUI特性，重新父化到aspect2d
                    self.node.wrtReparentTo(self.world.aspect2d)
                    print(f"2D GUI元素保持在aspect2d下")
            else:
                # 如果新父节点为None，重新父化到aspect2d
                self.node.wrtReparentTo(self.world.aspect2d)
                print(f"2D GUI元素重新父化到aspect2d")
        else:
            # 普通3D节点的处理
            if self.new_parent and not self.new_parent.isEmpty():
                self.node.wrtReparentTo(self.new_parent)
            else:
                # 如果新父节点为空，将其父化到render节点
                if self.world:
                    self.node.wrtReparentTo(self.world.render)
                else:
                    # 备用方案
                    from panda3d.core import NodePath
                    self.node.wrtReparentTo(NodePath("render"))

    def undo(self):
        """
        撤销重新父化操作
        """
        # 在改变父节点前保存当前的缩放值
        current_scale = self.node.getScale()

        # 恢复Panda3D节点父子关系
        if self.is_2d_gui and self.world:
            # 2D GUI元素需要特殊处理
            if self.old_parent and not self.old_parent.isEmpty():
                if hasattr(self.old_parent, 'getTag') and self.old_parent.getTag("is_gui_element") == "1":
                    # 原父节点是GUI元素，直接重新父化
                    self.node.wrtReparentTo(self.old_parent)
                else:
                    # 原父节点是3D节点，保持GUI特性，重新父化到aspect2d
                    self.node.wrtReparentTo(self.world.aspect2d)
                    print(f"2D GUI元素恢复到aspect2d下")
            else:
                # 如果原父节点为空，重新父化到aspect2d
                self.node.wrtReparentTo(self.world.aspect2d)
                print(f"2D GUI元素恢复到aspect2d")
        else:
            # 普通3D节点的处理
            if self.old_parent and not self.old_parent.isEmpty():
                self.node.wrtReparentTo(self.old_parent)
            else:
                # 如果原父节点为空，将其父化到render节点
                if self.world:
                    self.node.wrtReparentTo(self.world.render)
                else:
                    # 备用方案
                    from panda3d.core import NodePath
                    self.node.wrtReparentTo(NodePath("render"))

        # 恢复局部坐标（不是世界坐标），因为父节点已经改变
        self.node.setPos(self.local_pos)
        self.node.setHpr(self.local_hpr)
        # 特别处理缩放，确保GUI元素的缩放不会异常变化
        if not self.is_2d_gui or abs(current_scale.length() - self.local_scale.length()) > 0.001:
            self.node.setScale(self.local_scale)

    def redo(self):
        """
        重做重新父化操作
        """
        # 在改变父节点前保存当前的缩放值
        current_scale = self.node.getScale()

        # 重新执行Panda3D节点父子关系更新
        if self.is_2d_gui and self.world:
            # 2D GUI元素需要特殊处理
            if self.new_parent and not self.new_parent.isEmpty():
                if hasattr(self.new_parent, 'getTag') and self.new_parent.getTag("is_gui_element") == "1":
                    # 目标是GUI元素，直接重新父化
                    self.node.wrtReparentTo(self.new_parent)
                else:
                    # 目标是3D节点，保持GUI特性，重新父化到aspect2d
                    self.node.wrtReparentTo(self.world.aspect2d)
                    print(f"2D GUI元素保持在aspect2d下")
            else:
                # 如果新父节点为None，重新父化到aspect2d
                self.node.wrtReparentTo(self.world.aspect2d)
                print(f"2D GUI元素重新父化到aspect2d")
        else:
            # 普通3D节点的处理
            if self.new_parent and not self.new_parent.isEmpty():
                self.node.wrtReparentTo(self.new_parent)
            else:
                # 如果新父节点为空，将其父化到render节点
                if self.world:
                    self.node.wrtReparentTo(self.world.render)
                else:
                    # 备用方案
                    from panda3d.core import NodePath
                    self.node.wrtReparentTo(NodePath("render"))

        # 恢复局部坐标（不是世界坐标），因为父节点已经改变
        self.node.setPos(self.local_pos)
        self.node.setHpr(self.local_hpr)
        # 特别处理缩放，确保GUI元素的缩放不会异常变化
        if not self.is_2d_gui or abs(current_scale.length() - self.local_scale.length()) > 0.001:
            self.node.setScale(self.local_scale)


class CompositeCommand(Command):
    """
    组合命令类，用于同时执行多个命令
    """
    def __init__(self,commands:List[Command]):
        self.commands = commands

    def execute(self):
        """
        执行所有命令
        """
        for command in self.commands:
            command.execute()

    def undo(self):
        """
        撤销所有命令（逆序执行）
        """
        for command in reversed(self.commands):
            command.undo()

    def redo(self):
        """
        重做所有命令
        """
        for command in self.commands:
            command.redo()

class MoveLightCommand(Command):
    def __init__(self, node, old_pos, new_pos, light_object=None):
        self.node = node
        self.old_pos = Point3(old_pos)
        self.new_pos = Point3(new_pos)
        self.light_object = light_object

    def execute(self):  # 将原来的 do() 改为 execute()
        if self.light_object:
            self.light_object.pos = self.new_pos
        if self.node:
            self.node.setPos(self.new_pos)

    def undo(self):
        if self.light_object:
            self.light_object.pos = self.old_pos
        if self.node:
            self.node.setPos(self.old_pos)

    def redo(self):
        self.execute()  # 调用 execute() 而不是 do()



