from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from direct.task.TaskManagerGlobal import taskMgr

from QMeta3D.QMeta3D_Buttons_Translation import QMeta3D_Button_translation
from QMeta3D.QMeta3D_Keys_Translation import QMeta3D_Key_translation
from QMeta3D.QMeta3D_Modifiers_Translation import QMeta3D_Modifier_translation

__all__ = ["QMeta3DWidget"]


class QMeta3DSynchronizer(QTimer):
    def __init__(self, qMeta3DWidget, FPS=60):
        QTimer.__init__(self)
        self.qMeta3DWidget = qMeta3DWidget
        dt = 1000 / FPS
        self.setInterval(int(dt))
        self.timeout.connect(self.tick)

    def tick(self):
        try:
            # 在渲染前清理可能损坏的TransformState对象
            from panda3d.core import TransformState
            TransformState.clear_cache()
            
            taskMgr.step()
            self.qMeta3DWidget.update()
        except AssertionError as e:
            # 专门处理 TransformState has_mat() 断言错误
            if "has_mat" in str(e):
                print(f"警告: 检测到TransformState断言错误，已静默处理: {e}")
                # 尝试恢复渲染状态
                try:
                    # 强制清理缓存并重试
                    from panda3d.core import TransformState, RenderState
                    TransformState.clear_cache()
                    RenderState.clear_cache()
                    taskMgr.step()
                    self.qMeta3DWidget.update()
                except:
                    pass
            else:
                # 重新抛出其他断言错误
                raise
        except Exception as e:
            # 静默处理其他所有异常
            print(f"警告: 检测到异常，已静默处理: {e}")


def get_panda_key_modifiers(evt):
    panda_mods = []
    qt_mods = evt.modifiers()
    for qt_mod, panda_mod in QMeta3D_Modifier_translation.items():
        if (qt_mods & qt_mod) == qt_mod:
            panda_mods.append(panda_mod)
    return panda_mods


def get_panda_key_modifiers_prefix(evt):
    # join all modifiers (except NoModifier, which is None) with '-'
    prefix = "-".join([mod for mod in get_panda_key_modifiers(evt) if mod is not None])

    # if the prefix is not empty, append a '-'
    if prefix:
        prefix += '-'

    return prefix


class QMeta3DWidget(QWidget):
    def __init__(self, Meta3DWorld, parent=None, FPS=60, debug=False):
        QWidget.__init__(self, parent)
        self.rp_sync_requested = False
        # set fixed geometry
        self.Meta3DWorld = Meta3DWorld
        self.Meta3DWorld.set_parent(self)
        # Setup a timer in Qt that runs taskMgr.step() to simulate Panda's own main loop
        # pandaTimer = QTimer(self)
        # pandaTimer.timeout.connect()
        # pandaTimer.start(0)

        self.setFocusPolicy(Qt.StrongFocus)

        # Setup another timer that redraws this widget in a specific FPS
        # redrawTimer = QTimer(self)
        # redrawTimer.timeout.connect(self.update)
        # redrawTimer.start(1000/FPS)

        self.paintSurface = QPainter()
        self.rotate = QTransform()
        self.rotate.rotate(180)
        self.out_image = QImage()

        size = self.Meta3DWorld.cam.node().get_lens().get_film_size()
        self.initial_film_size = QSizeF(size.x, size.y)
        self.initial_size = self.size()

        self.synchronizer = QMeta3DSynchronizer(self, FPS)
        self.synchronizer.start()

        self.debug = debug

    def mousePressEvent(self, evt):
        button = evt.button()
        try:
            b = "{}{}".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Button_translation[button])
            if self.debug:
                print(b)
            messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
        except:
            print("Unimplemented button. Please send an issue on github to fix this problem")

    def mouseMoveEvent(self, evt:QtGui.QMouseEvent):
        button = evt.button()
        try:
            b = "mouse-move"
            if self.debug:
                print(b)
            messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
        except:
            print("Unimplemented button. Please send an issue on github to fix this problem")


    def mouseReleaseEvent(self, evt):
        button = evt.button()
        try:
            b = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Button_translation[button])
            if self.debug:
                print(b)
            messenger.send(b,[{"x":evt.x(),"y":evt.y()}])
        except:
            print("Unimplemented button. Please send an issue on github to fix this problem")

    def wheelEvent(self, evt):
        delta = evt.angleDelta().y()
        try:
            if self.debug:
                print(f"wheel {delta}")
            messenger.send('wheel',[{"delta":delta}])
        except:
            print("Unimplemented button. Please send an issue on github to fix this problem")

    def keyPressEvent(self, evt):
        key = evt.key()
        try:
            k = "{}{}".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Key_translation[key])
            if self.debug:
                print(k)
            messenger.send(k)
        except:
            print("Unimplemented key. Please send an issue on github to fix this problem")

    def keyReleaseEvent(self, evt):
        key = evt.key()
        try:
            k = "{}{}-up".format(get_panda_key_modifiers_prefix(evt), QMeta3D_Key_translation[key])
            if self.debug:
                print(k)
            messenger.send(k)
        except:
            print("Unimplemented key. Please send an issue on github to fix this problem")

    def resizeEvent(self, evt):
        width = evt.size().width()
        height = evt.size().height()
        #print(f"width:{width}")
        #print(f"height:{height}")

        from Meta3DWorld import resize_buffer

        lens = self.Meta3DWorld.cam.node().get_lens()

    def minimumSizeHint(self):
        return QSize(400, 300)

    def paintEvent(self, event):
        # 正常窗口渲染模式下，Panda3D 会直接渲染到窗口
        # 不需要从纹理复制数据
        # 只需要确保渲染循环继续运行
        
        # 触发一次渲染更新
        if hasattr(self.Meta3DWorld, 'win') and self.Meta3DWorld.win:
            # 确保窗口大小匹配
            width = self.width()
            height = self.height()
            
            # 如果窗口大小不匹配，更新窗口属性
            if (self.Meta3DWorld.win.get_x_size() != width or 
                self.Meta3DWorld.win.get_y_size() != height):
                from panda3d.core import WindowProperties
                props = WindowProperties()
                props.setSize(width, height)
                self.Meta3DWorld.win.request_properties(props)
        
        # 继续下一次更新
        self.update()

    def sync_Meta3d_window_size(self, width, height):
        try:
            from panda3d.core import WindowProperties

            # 更新窗口属性
            props = WindowProperties()
            props.setSize(width, height)

            # 对于正常窗口渲染，直接更新窗口属性
            if self.Meta3DWorld.win:
                self.Meta3DWorld.win.request_properties(props)

                # 如果使用 RenderPipeline，更新相关设置
                if hasattr(self.Meta3DWorld, 'render_pipeline'):
                    try:
                        from RenderPipelineFile.rpcore.globals import Globals
                        from panda3d.core import LVecBase2i
                        
                        # 更新全局分辨率
                        Globals.native_resolution = LVecBase2i(width, height)
                        
                        # 重新计算渲染分辨率
                        self.Meta3DWorld.render_pipeline._compute_render_resolution()
                        
                        # 通知各个管理器处理尺寸变化
                        self.Meta3DWorld.render_pipeline.light_mgr.compute_tile_size()
                        self.Meta3DWorld.render_pipeline.stage_mgr.handle_window_resize()
                        
                        if hasattr(self.Meta3DWorld.render_pipeline, 'debugger'):
                            self.Meta3DWorld.render_pipeline.debugger.handle_window_resize()
                        
                        # 触发插件的窗口尺寸变化钩子
                        self.Meta3DWorld.render_pipeline.plugin_mgr.trigger_hook("window_resized")
                    except Exception as rp_e:
                        print(f"RenderPipeline 尺寸更新失败: {rp_e}")
        except Exception as e:
            print(f"同步 Meta3D 窗口尺寸失败: {str(e)}")