from typing import Optional
import math

from .__prelude__ import Qt, Matrix, Point, Scene, logger

class GLWidget(Qt.QOpenGLWidget):
    positionChanged = Qt.Signal(tuple)
    resized_viewport = Qt.Signal(int, int)

    def __init__(self, scene_info: Scene):
        super().__init__()
        self.setFocusPolicy(Qt.Qt.FocusPolicy.WheelFocus)
        self.setMouseTracking(True)
        self._scene: Scene  = scene_info
        self._scene.renderChanged.connect(self.update)
        self._initialized_scene = None
        self._initialized_scene_mods = []

        self._clip_to_pixel: Optional[Matrix]  = None
        self.scene_mode = None

    @property
    def scene(self):
        return self._scene
    @property
    def initialized_scene(self):
        return self._initialized_scene

    @property
    def clip_to_pixel(self) -> Matrix:
        if self._clip_to_pixel is None:
            w, h = float(self.width()), float(self.height())
            self._clip_to_pixel = Matrix.scale((w/2.0, -h/2.0, 1.0)) * Matrix.translate((1.0,-1.0))
        return self._clip_to_pixel

    @property
    def world_to_clip(self):
        return self.initialized_scene.world_to_clip

    def sizeHint(self):
        return Qt.QSize(100,100)

    def pixel_to_model(self, x, y) -> tuple[float,float]:
        clip_x, clip_y = self.clip_to_pixel.inverse().transform_point((x, y))
        return self.initialized_scene.clip_to_model(clip_x, clip_y)
    def event_clip_position(self, event):
        position = event.position()
        return self.clip_to_pixel.inverse().transform_point((position.x(), position.y()))

    def enterEvent(self, event):
        self.setFocus()
        self.scene_mode = self.scene_mode.on_mouse_enter(self.event_clip_position(event))
    def mouseMoveEvent(self, event):
        clip_coords = self.event_clip_position(event)
        self.positionChanged.emit(self.pixel_to_model(event.position().x(), event.position().y()))
        self.scene_mode = self.scene_mode.on_mouse_move(clip_coords)
    def leaveEvent(self, event):
        self.positionChanged.emit(None)
        self.scene_mode = self.scene_mode.on_mouse_leave()
    def mousePressEvent(self, event):
        self.scene_mode = self.scene_mode.on_mouse_button_pressed(self.event_clip_position(event), event.button())
    def mouseReleaseEvent(self, event):
        self.scene_mode = self.scene_mode.on_mouse_button_released(self.event_clip_position(event), event.button())
    def keyPressEvent(self, event):
        self.scene_mode = self.scene_mode.on_key_pressed(event.key())
    def keyReleaseEvent(self, event):
        self.scene_mode = self.scene_mode.on_key_released(event.key())
    def focusInEvent(self, event):
        self.scene_mode = self.initialized_scene.idle_mode()
    def focusOutEvent(self, event):
        self.positionChanged.emit(None)
        self.scene_mode = self.initialized_scene.idle_mode()

    def preserving_pointer(self, pos, doit):
        x_b, y_b = self.pixel_to_model(pos.x(), pos.y())
        doit()
        x_a, y_a = self.pixel_to_model(pos.x(), pos.y())
        self._scene.center += Point(x_a - x_b, y_a - y_b)

    def wheelEvent(self, event):
        angle_delta = event.angleDelta()
        self.scene_mode = self.scene_mode.on_mouse_wheel(self.event_clip_position(event), angle_delta.x(), angle_delta.y())

    def modify_initialized_scene(self, mod):
        if self._initialized_scene is None:
            self._initialized_scene_mods.append(mod)
        else:
            mod(self._initialized_scene)

    @property
    def initialized_scene(self):
        if self._initialized_scene is None:
            self._initialized_scene = self._scene.initializeDrawing(self.context())
            for mod in self._initialized_scene_mods:
                mod(self._initialized_scene)
            self._initialized_scene_mods = []
            self.scene_mode = self._initialized_scene.idle_mode()
        return self._initialized_scene

    def initializeGL(self):
        _ = self.initialized_scene

    def resizeGL(self, w, h):
        self._clip_to_pixel = None
        self.initialized_scene.resizeViewport(w, h)
        self.resized_viewport.emit(w,h)

    def paintGL(self):
        ret = self.initialized_scene.paintGL()
        return ret

    def closeEvent(self, event):
        event.accept()
        if self._initialized_scene is not None:
            self.context().makeCurrent(Qt.QOffscreenSurface())
            self._initialized_scene.freeGL()
