import tempfile
import time

import numpy as np
import pyqtgraph as pg
from pyqtgraph import mkBrush, mkPen
from qgis.PyQt.QtCore import QRect, QRectF, QTimer
from qgis.PyQt.QtGui import QImage, QTransform

from openlog.datamodel.assay.generic_assay import GenericAssay
from openlog.gui.pyqtgraph.CustomBarGraphItem import CustomBarGraphItem


class ImageryImageItem(pg.GraphicsObject):
    def __init__(self, image: QImage, x_start: float, x_end: float):
        """
        Implementation of pg.GraphicsObject to display image for fixed image ratio with :
        - minimum image width in pixel (l_min)
        - maximum stretch factor (l_max_stretch_factor)
        """
        super().__init__()
        self.image = image
        self.l_min = 150.0
        self.l_max_stretch_factor = 10.0

        # Define coordinates to respect image ratio
        ratio = self.image.height() / self.image.width()
        new_height = x_end - x_start
        new_width = new_height / ratio
        self.image_width_m = new_width

        # Define transform with current x_start / x_end
        tr = QTransform()
        tr.translate(0, x_start)
        tr.scale(new_width / self.image.width(), new_height / self.image.height())
        self.setTransform(tr)

        # Add timer for image transform update on viewTransformChanged
        self._update_image_timer = QTimer()
        self._update_image_timer.setSingleShot(True)
        self._update_image_timer.timeout.connect(self._update_image_transform)

    def boundingRect(self):
        if self.image is None:
            return QRectF(0.0, 0.0, 0.0, 0.0)
        return QRectF(0.0, 0.0, float(self.image.width()), float(self.image.height()))

    def paint(self, p, *args):
        if self.image:
            p.drawImage(
                QRectF(0, 0, self.image.width(), self.image.height()), self.image
            )

    def set_min_width(self, min_width: int) -> None:
        """Define minimum width in pixel for image

        Args:
            min_width: (int) image minium width in pixel
        """
        self.l_min = min_width
        self._update_image_transform()

    def set_max_stretch_factor(self, max_factor: float) -> None:
        """Define maximum stretch factor for image

        Args:
            max_factor: (float) max. stretch factor
        """
        self.l_max_stretch_factor = max_factor
        self._update_image_transform()

    def viewTransformChanged(self) -> None:
        """
        Update transform for image

        """
        # Use timer to avoid multiple transform update
        self._update_image_timer.stop()
        self._update_image_timer.start(75)
        super().viewTransformChanged()

    def _update_image_transform(self) -> None:
        """
        Update image transform for correct image ratio

        """
        if not self.image:
            return

        # Get current image display ratio
        A = self.image.width() / self.image.height()

        # Get current view width and height in pixels
        widget = self.getViewWidget()
        screen_geom = widget.getViewBox().screenGeometry()
        l = screen_geom.width()
        h = screen_geom.height()

        # Get current view range in item coordinates
        y_range = widget.viewRange()[1]
        coordinate_range = y_range[1] - y_range[0]
        if coordinate_range == 0:
            return

        # Define item height in coordinates from transformation
        t = self.transform()
        horizontal_scaling = t.m22()
        item_height = horizontal_scaling * self.image.height()

        # Calculate height in pixel from item height
        h_image = h * item_height / coordinate_range

        # Define width in pixel with image display ratio
        l_image = h_image * A

        # Apply factor to use maximum available width
        l_image = min(l, l_image * self.l_max_stretch_factor)

        # Define image minimum width
        l_image = max(l_image, self.l_min)

        # Define range from screen geometry width and image width in meters
        image_range_m = (l / l_image) * self.image_width_m
        widget.setXRange(0, image_range_m)


class ImageryGraphItems:
    def __init__(self, assay: GenericAssay, column: str):
        """
        Store multiple plot items to display assay data as imagery series for extended data :
        - multiple pg.ImageItem to display imagery

        Args:
            assay: GenericAssay
            column: assay column name (should have an extended data extent and nominal or categorical series type)
        """
        self._assay = assay
        self._column = column
        self._image_items = []

        # For now display all values available
        (x, y) = assay.get_all_values(column, remove_none=True)
        x = x.astype(float)

        # Get image format from assay definition
        image_format_col = assay.assay_definition.columns[column].image_format_col
        (_, image_format) = assay.get_all_values(image_format_col)

        for i, image_binary in enumerate(y):
            x_start = x[i][0]
            x_end = x[i][1]

            # Create image from binary data
            temp_file = tempfile.NamedTemporaryFile(mode="wb", suffix=image_format[i])
            image_filename = temp_file.name
            temp_file.close()
            with open(image_filename, "wb") as fobj:
                fobj.write(image_binary)
            img = QImage(image_filename)

            # Add item to current images list
            image_item = ImageryImageItem(img, x_start, x_end)
            self._image_items.append(image_item)

    def add_to_plot(self, plot: pg.PlotWidget) -> None:
        """
        Add stored items to plot

        Args:
            plot: pg.PlotWidget
        """
        for image_item in self._image_items:
            plot.addItem(image_item)

    def setVisible(self, visible: bool) -> None:
        """
        Define store object visibility

        Args:
            visible: True to display items, False otherwise
        """
        for image_item in self._image_items:
            image_item.setVisible(visible)

    def set_min_width(self, min_width: int) -> None:
        """Define minimum width in pixel for images

        Args:
            min_width: (int) image minium width in pixel
        """
        for image_item in self._image_items:
            image_item.set_min_width(min_width)

    def set_max_stretch_factor(self, max_factor: float) -> None:
        """Define maximum stretch factor for images

        Args:
            max_factor: (float) max. stretch factor
        """
        for image_item in self._image_items:
            image_item.set_max_stretch_factor(max_factor)
