import pyqtgraph as pq
from qgis.PyQt.QtCore import QRectF, QTimer
from qgis.PyQt.QtGui import QBrush, QColor, QImage, QPainter, QTransform
from qgis.PyQt.QtSvg import QSvgRenderer


class SvgBarGraphItem(pq.BarGraphItem):
    def __init__(self, **opts):
        """
        Override pq.BarGraphItem to display svg pattern in each graph bars

        To maintain a correct display of wanted svg size, brush transform ratio is computed at each view transform changes

        Additional keyword options are:
        svg_files, svg_sizes, brushes

        Example uses:

            SvgBarGraphItem(x=range(3), height=[1,5,2], width=0.5,
                            svg_files= ['pattern1.svg','pattern1.svg','pattern3.svg'],
                            svg_sizes= [100,150,200],
                            brushes= ['red', 'blue', 'yellow'])

        Args:
            **opts:
        """
        pq.BarGraphItem.__init__(self, **opts)

        # Map of images of svg files render with different option (size,color)
        self.images = {}

        # Define options
        self.svg_sizes = None
        self.svg_files = None

        if "svg_files" in opts:
            self.svg_files = opts["svg_files"]
        if "svg_sizes" in opts:
            self.svg_sizes = opts["svg_sizes"]

        # Add timer for brush update on viewTransformChanged
        self._update_brush_timer = QTimer()
        self._update_brush_timer.setSingleShot(True)
        self._update_brush_timer.timeout.connect(self._update_brush_transform)
        # Update brush texture to use svg files
        if self.svg_files is not None:
            self.update_brush_texture()

    def update_brush_texture(self) -> None:
        """
        Update brush texture with current svg files

        """
        default_brush = self.opts["brushes"]
        brushes = []

        for i in range(len(self.svg_files)):
            color = default_brush[i]
            svg_file = self.svg_files[i]
            svg_size = self._get_item_svg_size(i)

            # Render svg file if not available at this size and color
            if (svg_file, svg_size, color) not in self.images:
                self._render_svg_image(svg_file, svg_size, color)

            # Update bar brush
            brush = QBrush()
            brush.setTextureImage(self.images[(svg_file, svg_size, color)])
            brushes.append(brush)
        self.setOpts(brushes=brushes)
        self._update_brush_transform()

    def viewTransformChanged(self) -> None:
        """
        Update transform for each brush depending on svg size and current device

        """
        # Use timer to avoid multiple brush update
        self._update_brush_timer.stop()
        self._update_brush_timer.start(75)

    def _update_brush_transform(self):
        """
        Update brush transform for correct svg display

        """
        reset_picture = False
        for i in range(len(self.opts["brushes"])):
            wanted_pix_w = self._get_item_svg_size(i)
            wanted_pix_h = wanted_pix_w

            svg_rect = QRectF(0, 0, wanted_pix_w, wanted_pix_h)
            svg_rect_device = self.mapRectFromDevice(svg_rect)
            if svg_rect_device is not None:
                ratio_w = wanted_pix_w / svg_rect_device.width()
                ratio_h = wanted_pix_h / svg_rect_device.height()

                brush = self.opts["brushes"][i]
                current_transform = brush.transform()
                transform = QTransform()
                transform = transform.scale(1 / ratio_w, 1 / ratio_h)
                if current_transform != transform:
                    brush.setTransform(transform)
                    self.opts["brushes"][i] = brush
                    reset_picture = True

        if reset_picture:
            self.prepareGeometryChange()
            self.picture = None

    def _render_svg_image(self, svg_file: str, svg_size: int, color: str) -> None:
        """
        Render svg image and store it in images map

        Args:
            svg_file: (str) svg file path
            svg_size: (int) wanted svg size
            color: (str) color as html string
        """
        renderer = QSvgRenderer(svg_file)
        default_size = renderer.defaultSize()
        ratio = default_size.height() / default_size.width()
        hSize = int(svg_size * ratio)
        image = QImage(svg_size, hSize, QImage.Format_ARGB32_Premultiplied)
        image.fill(QColor(color))
        impainter = QPainter(image)
        renderer.render(impainter)
        impainter.end()
        self.images[(svg_file, svg_size, color)] = image

    def _get_item_svg_size(self, i: int) -> str:
        """
        Get item svg size : if not defined use 150

        Args:
            i: bar graph item index

        Returns: (int) svg size

        """
        svg_sizes = 150
        if self.svg_sizes is not None:
            svg_sizes = self.svg_sizes[i]
        return svg_sizes
