import moderngl as mgl
import numpy as np
import sys
import threading
import time

try:
    from PySide6.QtCore import QPoint, QEvent, QLibraryInfo, qVersion, Qt, QElapsedTimer
    from PySide6.QtWidgets import QApplication, QWidget
    from PySide6.QtOpenGLWidgets import QOpenGLWidget
    from PySide6.QtGui import QSurfaceFormat, QMouseEvent, QInputEvent, QKeyEvent, QTouchEvent, QShortcut
except ImportError:
    from PySide2.QtCore import QPoint, QEvent, QLibraryInfo, qVersion, Qt, QElapsedTimer
    from PySide2.QtWidgets import QApplication, QWidget, QOpenGLWidget, QShortcut
    from PySide2.QtGui import QSurfaceFormat, QMouseEvent, QInputEvent, QKeyEvent, QTouchEvent
    from PySide2.QtWidgets import QApplication, QWidget


opengl_version = (3, 3)


running = True


opt_quiet = False


def background():
    timer = QElapsedTimer()
    timer.start()
    last = 0
    while running:
        time.sleep(0.1)
        e = timer.elapsed()
        dt = e - last
        if dt > 105:
            print('background', dt, "ms!")
        last = e


class View(QOpenGLWidget):
    def __init__(self, parent=None):
        print('>__init__()')
        super().__init__(parent)
        fmt = QSurfaceFormat()
        fmt.setVersion(*opengl_version)
        fmt.setProfile(QSurfaceFormat.CoreProfile)
        fmt.setSamples(4)
        self.setFormat(fmt)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setAttribute(Qt.WA_AcceptTouchEvents, True)
        self.timer = QElapsedTimer()
        self.timer.start()
        self.last_time = 0
        print('<__init__()')

    # -- opengl stuff --

    def init(self):
        print('>init()')
        self.makeCurrent()
        # the framebuffer is already created and sized by Qt
        assert self.ctx, 'context is not initialized'
        dfb = self.defaultFramebufferObject()
        self.frame_buffer = self.ctx.detect_framebuffer(dfb)
        print('<init()')

    def load(self):
        print('>load()')
        self.shader = self.ctx.program(vertex_shader='''
            #version 330
            in vec3 position;
            out vec3 colorized;
            void main() {
                colorized = abs(position);
                gl_Position = vec4(position.xy/position.z, 1-0.001/position.z, 1);
            }
            ''', fragment_shader='''
            #version 330
            in vec3 colorized;
            uniform vec4 color;
            // render color
            out vec4 out_color;
            void main() {
                out_color = color + vec4(colorized,0);
            }
            ''',)

        # a lot of 2D triangles to display
        points = (np.random.random((10000, 3, 3)) - 0.5) + [(0, 0, 1)]
        vertex_buffer = self.ctx.buffer(points.astype('f4'))
        self.vertex_array = self.ctx.vertex_array(self.shader, [(vertex_buffer, '3f', 'position')])
        print('<load()')

    # -- event system --
    def closeEvent(self, evt):
        global running
        running = False
        evt.accept()

    def inputEvent(self, evt):
        if not opt_quiet:
            print("inputEvent", evt)
        ''' Default handler for every input event (mouse move, press, release,
            keyboard, ...)
            When the event is not accepted, the usual matching Qt handlers
            are used (mousePressEvent, KeyPressEvent, etc).

            This function can be overwritten to change the view widget behavior.
        '''
        # for the example:  only triggers a rendering
        self.update()

    def mousePressEvent(self, evt):
        evt.accept()

    def mouseMoveEvent(self, evt):
        evt.accept()
        self.update()

    # -- Qt stuff --

    #   /!\  comment this method to remove any thread-blocking issues
    def event(self, evt):
        if not opt_quiet:
            print(">event", evt)
        st = self.timer.elapsed()
        if isinstance(evt, QInputEvent):
            evt.ignore()
            self.inputEvent(evt)
            if evt.isAccepted():
                return True
        r = super().event(evt)
        dt = self.timer.elapsed() - st
        if dt > 0 and not opt_quiet:
            print("<event", evt.type(), dt, "ms")
        return r

    def initializeGL(self):
        print('>initializeGL')
        self.ctx = mgl.create_context()
        self.init()
        self.load()
        print('<initializeGL')

    def paintGL(self):
        # set the opengl current context from Qt (doing it only from moderngl interferes with Qt)
        st = self.timer.elapsed()
        print('>paintGL')
        self.makeCurrent()

        # prepare the view uniforms
        self.ctx.multisample = True
        self.ctx.enable_only(mgl.BLEND)
        self.ctx.blend_func = mgl.SRC_ALPHA, mgl.ONE_MINUS_SRC_ALPHA
        self.ctx.blend_equation = mgl.FUNC_ADD
        self.frame_buffer.use()
        self.frame_buffer.clear(0, 0, 0, 0)
        self.shader['color'] = (0.3, 0.8, 0.6, 0.1)
        # put a bunch of render calls, so the GPU gets busy
        for i in range(50):
            self.vertex_array.render()

        # we are not waiting here for the GPU to complete the draw calls we just sent.
        # self.ctx.finish()  # this waits for the GPU
        dt = self.timer.elapsed() - st
        print('<paintGL', dt, "ms")

    def resizeEvent(self, evt):
        st = self.timer.elapsed()
        print('>resizeEvent')
        super().resizeEvent(evt)
        self.init()
        self.update()
        dt = self.timer.elapsed() - st
        print('<resizeEvent', dt, "ms")


if __name__ == '__main__':
    print('Python {}.{}'.format(sys.version_info[0], sys.version_info[1]))
    print(QLibraryInfo.build())

    opt_quiet = "-q" in sys.argv
    thread = threading.Thread(target=background) if "-t" not in sys.argv else None

    app = QApplication(sys.argv)
    win = View()
    win.setWindowTitle(qVersion())
    win.show()
    sc = QShortcut(Qt.CTRL | Qt.Key_Q, win)
    sc.activated.connect(win.close)

    if thread:
        thread.start()
    app.exec_()
    if thread:
        thread.join()
