class SelectionScene(QGraphicsScene):

    selection_drawing_finished = pyqtSignal(QRectF)

    def __init__(self, scene_rect: QRectF, parent: QObject = None):
        super(SelectionScene, self).__init__(scene_rect, parent)
        rectangle_color = QColor(Qt.red)
        self.default_border_pen = QPen(Qt.red, 3, Qt.SolidLine, Qt.SquareCap,
                                       Qt.MiterJoin)
        self.default_fill_brush = QBrush(rectangle_color, Qt.BDiagPattern)
        self.new_selection_view: QGraphicsRectItem = None
        self.new_selection_origin: QPointF = None
        self.mode = EditorMode.DRAW_MODE

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == EditorMode.DRAW_MODE:
            self._handle_mouse_press_draw_mode(event)
        elif self.mode == EditorMode.MOVE_MODE:
            raise NotImplementedError("Move mode is not implemented!")

    def _handle_mouse_press_draw_mode(self, event: QGraphicsSceneMouseEvent):
        if event.button() == Qt.LeftButton and self.new_selection_view is None:
            self.new_selection_origin = event.scenePos()
            self._restrict_to_scene_space(self.new_selection_origin)
            scene_logger.info(
                f"Beginning to draw a new selection: "
                f"X={self.new_selection_origin.x()}, Y={self.new_selection_origin.y()}"
            )
            self.new_selection_view = QGraphicsRectItem(
                self.new_selection_origin.x(), self.new_selection_origin.y(),
                0, 0)
            self.new_selection_view.setPen(self.default_border_pen)
            self.new_selection_view.setBrush(self.default_fill_brush)
            self.addItem(self.new_selection_view)
            event.accept()

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == EditorMode.DRAW_MODE:
            self._handle_mouse_move_draw_mode(event)
        elif self.mode == EditorMode.MOVE_MODE:
            raise NotImplementedError("Move mode is not implemented!")

    def _handle_mouse_move_draw_mode(self, event: QGraphicsSceneMouseEvent):
        point2 = event.scenePos()
        self._restrict_to_scene_space(point2)
        if self.new_selection_origin is None:
            scene_logger.warning("Move event: Selection origin is None!")
            event.accept()
            return

        rectangle = QRectF(
            QPointF(min(self.new_selection_origin.x(), point2.x()),
                    min(self.new_selection_origin.y(), point2.y())),
            QPointF(max(self.new_selection_origin.x(), point2.x()),
                    max(self.new_selection_origin.y(), point2.y())))
        self.new_selection_view.setRect(rectangle)
        event.accept()

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
        if self.mode == EditorMode.DRAW_MODE:
            self._handle_mouse_release_draw_mode(event)
        elif self.mode == EditorMode.MOVE_MODE:
            raise NotImplementedError("Move mode is not implemented!")

    def _handle_mouse_release_draw_mode(self, event: QGraphicsSceneMouseEvent):
        self.new_selection_view: QGraphicsRectItem
        if event.button(
        ) == Qt.LeftButton and self.new_selection_view is not None:
            absolute_rectangle = self.new_selection_view.mapRectFromScene(
                self.new_selection_view.rect())
            if self.is_rectangle_valid_selection(absolute_rectangle):
                self.selection_drawing_finished.emit(absolute_rectangle)
            else:
                scene_logger.info(
                    f"Discarding invalid selection: "
                    f"x={absolute_rectangle.x()}, y={absolute_rectangle.y()}, "
                    f"width={absolute_rectangle.width()}, height={absolute_rectangle.height()}"
                )
                self.removeItem(self.new_selection_view)
            self.new_selection_origin = None
            self.new_selection_view = None
            event.accept()

    def load_selections(self, current: QModelIndex):
        selection_count: int = current.model().rowCount(
            current)  # The number of child nodes, which are selections
        current_first_column = current.sibling(
            current.row(), 0)  # Selections are below the first column
        selections: typing.List[Selection] = [
            current_first_column.child(index, 0).data(Qt.UserRole)
            for index in range(selection_count)
        ]
        editor_logger.debug(f"Loading selection list: {selections}")
        for selection in selections:
            self._draw_rectangle(current, selection)

    def _restrict_to_scene_space(self, point: QPointF):
        """Restrict rectangle drawing to the screen space. This prevents drawing out of the source image bounds."""
        point.setX(min(max(point.x(), 0), self.sceneRect().width()))
        point.setY(min(max(point.y(), 0), self.sceneRect().height()))

    def _draw_rectangle(self, current: QModelIndex, rectangle: Selection):
        self.addRect(self._to_local_coordinates(current, rectangle),
                     self.default_border_pen, self.default_fill_brush)

    def _to_local_coordinates(self, current: QModelIndex,
                              rectangle: Selection) -> QRectF:
        """
        Scales a model Selection to local coordinates. Large images are scaled down, so the rectangles need to be
        scaled, too. This function performs the scaling and conversion to floating point based rectangles, as expected
        by QGraphicsView.
        """
        scaling_factor: float = self.width() / current.sibling(
            current.row(), 0).data(Qt.UserRole).width

        if scaling_factor >= 1:
            result = rectangle.as_qrectf
        else:
            result = QRectF(rectangle.top_left.x * scaling_factor,
                            rectangle.top_left.y * scaling_factor,
                            rectangle.width * scaling_factor,
                            rectangle.height * scaling_factor)
            scene_logger.debug(
                f"Scaled {rectangle} to {result.topLeft(), result.bottomRight()}"
            )
        return result

    def is_rectangle_valid_selection(self, selection: QRectF) -> bool:
        """
        Returns True, if the given rectangle is a valid selection.
        A selection is determined to be valid if its width and height is at least 0.5% of the source image or 20 pixels
        large, whichever comes first
        """
        return (selection.width() > self.sceneRect().width() * 0.01 or selection.width() > 20) \
            and (selection.height() > self.sceneRect().height() * 0.01 or selection.height() > 20)