


from PyQt5.QtCore import Qt, QMetaObject, QPoint, QRect, QTimer, pyqtSignal
from PyQt5.QtGui import QColor, QCursor, QIcon, QImage, QPainter, QPixmap, QTransform
from PyQt5.QtWidgets import QWidget

from application.python.types import MarkerType
from math import ceil
from operator import truediv
from sipsimple.core import FrameBufferVideoRenderer

from blink.resources import Resources


__all__ = ['VideoSurface']


class Container(object):
    pass


class InteractionState(object):
    def __init__(self):
        self.moving = False
        self.resizing = False
        self.resize_corner = None
        self.mouse_last_position = None
        self.initial_geometry = None

    @property
    def active(self):
        return self.moving or self.resizing

    def clear(self):
        self.__init__()


class VideoSurface(QWidget):
    class TopLeftCorner(metaclass=MarkerType):     pass
    class TopRightCorner(metaclass=MarkerType):    pass
    class BottomLeftCorner(metaclass=MarkerType):  pass
    class BottomRightCorner(metaclass=MarkerType): pass

    adjusted = pyqtSignal(QRect, QRect)  # the widget was adjusted by the user (if interactive)

    interactive = False  # if the widget can be interacted with (moved, resized)
    mirror = False       # mirror the image horizontally

    def __init__(self, parent=None, framerate=None):
        super(VideoSurface, self).__init__(parent)
        self.setAttribute(Qt.WA_OpaquePaintEvent, True)
        self.setMouseTracking(True)
        self.cursors = Container()
        self.cursors.resize_top    = QCursor(QIcon(Resources.get('icons/resize-top.svg')).pixmap(16),    hotX=8,  hotY=0)
        self.cursors.resize_bottom = QCursor(QIcon(Resources.get('icons/resize-bottom.svg')).pixmap(16), hotX=8,  hotY=16)
        if framerate is not None:
            self._clock = QTimer()
            self._clock.setInterval(int(1000/framerate))
            self._clock.timeout.connect(self.update)
        else:
            self._clock = None
        self._interaction = InteractionState()
        self._image = None

    def __getattr__(self, name):
        if name == '_renderer':
            return self.__dict__.setdefault(name, FrameBufferVideoRenderer(self._handle_frame))
        raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))

    def _get_producer(self):
        return self._renderer.producer

    def _set_producer(self, producer):
        if self._clock is not None:
            self._clock.stop()
        self._renderer.producer = producer
        if self._clock is not None and producer is not None:
            self._clock.start()

    producer = property(_get_producer, _set_producer)
    del _get_producer, _set_producer

    @property
    def aspect(self):
        producer = self._renderer.producer
        return truediv(*producer.framesize) if producer is not None else 16/9

    def width_for_height(self, height):
        return int(ceil(height * self.aspect))

    def height_for_width(self, width):
        return int(ceil(width / self.aspect))

    def heightForWidth(self, width):
        return int(ceil(width * 9/16))

    def stop(self):
        if self._clock is not None:
            self._clock.stop()
        self._renderer.close()
        del self._renderer

    def _handle_frame(self, frame):
        self._image = QImage(frame.data, frame.width, frame.height, QImage.Format_ARGB32)
        if self._clock is None:
            QMetaObject.invokeMethod(self, 'update', Qt.QueuedConnection)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.fillRect(self.rect(), QColor('#101010'))

        image = self._image

        if image is not None:
            if self.height() < 240:
                fast_scaler = QTransform()
                scale = 297/image.height()
                if self.mirror:
                    fast_scaler.scale(-scale, scale)
                else:
                    fast_scaler.scale(scale, scale)
                rect = event.rect()
                painter.drawPixmap(rect, QPixmap.fromImage(image.transformed(fast_scaler)).scaledToHeight(self.height(), Qt.SmoothTransformation), rect)
            else:
                transform = QTransform()
                scale = min(self.width()/image.width(), self.height()/image.height())
                if self.mirror:
                    transform.translate((self.width() + image.width()*scale)/2, (self.height() - image.height()*scale)/2)
                    transform.scale(-scale, scale)
                else:
                    transform.translate((self.width() - image.width()*scale)/2, (self.height() - image.height()*scale)/2)
                    transform.scale(scale, scale)

                inverse_transform, invertible = transform.inverted()
                rect = inverse_transform.mapRect(event.rect()).adjusted(-1, -1, 1, 1).intersected(image.rect())

                painter.setTransform(transform)

                if self.height() > 400:
                    painter.drawPixmap(rect, QPixmap.fromImage(image), rect)
                else:
                    painter.drawImage(rect, image, rect)

        painter.end()

    def mousePressEvent(self, event):
        if self.interactive and event.button() == Qt.LeftButton and event.modifiers() == Qt.NoModifier:
            if self.rect().adjusted(0, 10, 0, -10).contains(event.pos()):
                self._interaction.moving = True
            else:
                self._interaction.resizing = True
                event_x = event.x()
                event_y = event.y()
                half_width = self.width() / 2
                half_height = self.height() / 2
                if event_x < half_width and event_y < half_height:
                    self._interaction.resize_corner = self.TopLeftCorner
                elif event_x >= half_width and event_y < half_height:
                    self._interaction.resize_corner = self.TopRightCorner
                elif event_x < half_width and event_y >= half_height:
                    self._interaction.resize_corner = self.BottomLeftCorner
                else:
                    self._interaction.resize_corner = self.BottomRightCorner
            self._interaction.mouse_last_position = event.globalPos()
            self._interaction.initial_geometry = self.geometry()

    def mouseReleaseEvent(self, event):
        if self._interaction.active and self._interaction.initial_geometry != self.geometry():
            self.adjusted.emit(self._interaction.initial_geometry, self.geometry())
        self._interaction.clear()

    def mouseMoveEvent(self, event):
        if self._interaction.moving:
            mouse_position = event.globalPos()
            offset = mouse_position - self._interaction.mouse_last_position
            if self.parent() is not None:
                parent_rect = self.parent().rect()
                old_geometry = self.geometry()
                new_geometry = old_geometry.translated(offset)
                if new_geometry.left() < 0:
                    new_geometry.moveLeft(0)
                if new_geometry.top() < 0:
                    new_geometry.moveTop(0)
                if new_geometry.right() > parent_rect.right():
                    new_geometry.moveRight(parent_rect.right())
                if new_geometry.bottom() > parent_rect.bottom():
                    new_geometry.moveBottom(parent_rect.bottom())
                offset = new_geometry.topLeft() - old_geometry.topLeft()
            self.move(self.pos() + offset)
            self._interaction.mouse_last_position += offset
        elif self._interaction.resizing:
            mouse_position = event.globalPos()
            delta_y = mouse_position.y() - self._interaction.mouse_last_position.y()
            geometry = self.geometry()

            if self._interaction.resize_corner is self.TopLeftCorner:
                delta_x = -(self.width_for_height(geometry.height() - delta_y) - geometry.width())
                geometry.setTopLeft(geometry.topLeft() + QPoint(delta_x, delta_y))
            elif self._interaction.resize_corner is self.TopRightCorner:
                delta_x = +(self.width_for_height(geometry.height() - delta_y) - geometry.width())
                geometry.setTopRight(geometry.topRight() + QPoint(delta_x, delta_y))
            elif self._interaction.resize_corner is self.BottomLeftCorner:
                delta_x = -(self.width_for_height(geometry.height() + delta_y) - geometry.width())
                geometry.setBottomLeft(geometry.bottomLeft() + QPoint(delta_x, delta_y))
            else:
                delta_x = +(self.width_for_height(geometry.height() + delta_y) - geometry.width())
                geometry.setBottomRight(geometry.bottomRight() + QPoint(delta_x, delta_y))

            if self.minimumHeight() <= geometry.height() <= self.maximumHeight() and (self.parent() is None or self.parent().rect().contains(geometry)):
                self.setGeometry(geometry)
                self._interaction.mouse_last_position = mouse_position
        elif self.interactive:
            mouse_position = event.pos()
            topbar_rect = QRect(0, 0, self.width(), 10)

            if self.rect().adjusted(0, 10, 0, -10).contains(mouse_position):
                self.setCursor(Qt.ArrowCursor)
            elif topbar_rect.contains(mouse_position):
                self.setCursor(self.cursors.resize_top)
            else:
                self.setCursor(self.cursors.resize_bottom)

    def closeEvent(self, event):
        super(VideoSurface, self).closeEvent(event)
        self.stop()

