# coding:utf-8
from enum import Enum

from PySide6.QtCore import QEvent, QObject, QPoint, QTimer, Qt, QPropertyAnimation, QModelIndex, QRect
from PySide6.QtGui import QColor, QHelpEvent
from PySide6.QtWidgets import (QApplication, QFrame, QGraphicsDropShadowEffect,
                             QHBoxLayout, QLabel, QWidget, QAbstractItemView, QStyleOptionViewItem,
                             QTableView)

from ...common import FluentStyleSheet
from ...common.screen import getCurrentScreenGeometry


class ToolTipPosition(Enum):
    """ Info bar position """

    TOP = 0
    BOTTOM = 1
    LEFT = 2
    RIGHT = 3
    TOP_LEFT = 4
    TOP_RIGHT = 5
    BOTTOM_LEFT = 6
    BOTTOM_RIGHT = 7


class ItemViewToolTipType(Enum):
    """ Info bar position """

    LIST = 0
    TABLE = 1


class ToolTip(QFrame):
    """ Tool tip """

    def __init__(self, text='', parent=None):
        """
        Parameters
        ----------
        text: str
            the text of tool tip

        parent: QWidget
            parent widget
        """
        super().__init__(parent=parent)
        self.__text = text
        self.__duration = 1000

        self.container = self._createContainer()
        self.timer = QTimer(self)

        self.setLayout(QHBoxLayout())
        self.containerLayout = QHBoxLayout(self.container)
        self.label = QLabel(text, self)

        # set layout
        self.layout().setContentsMargins(12, 8, 12, 12)
        self.layout().addWidget(self.container)
        self.containerLayout.addWidget(self.label)
        self.containerLayout.setContentsMargins(8, 6, 8, 6)

        # add opacity effect
        self.opacityAni = QPropertyAnimation(self, b'windowOpacity', self)
        self.opacityAni.setDuration(150)

        # add shadow
        self.shadowEffect = QGraphicsDropShadowEffect(self)
        self.shadowEffect.setBlurRadius(25)
        self.shadowEffect.setColor(QColor(0, 0, 0, 50))
        self.shadowEffect.setOffset(0, 5)
        self.container.setGraphicsEffect(self.shadowEffect)

        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.hide)

        # set style
        self.setAttribute(Qt.WA_TransparentForMouseEvents)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setAttribute(Qt.WA_ShowWithoutActivating)
        self.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.__setQss()

    def text(self):
        return self.__text

    def setText(self, text):
        """ set text on tooltip """
        self.__text = text
        self.label.setText(text)
        self.container.adjustSize()
        self.adjustSize()

    def duration(self):
        return self.__duration

    def setDuration(self, duration: int):
        """ set tooltip duration in milliseconds

        Parameters
        ----------
        duration: int
            display duration in milliseconds, if `duration <= 0`, tooltip won't disappear automatically
        """
        self.__duration = duration

    def __setQss(self):
        """ set style sheet """
        self.container.setObjectName("container")
        self.label.setObjectName("contentLabel")
        FluentStyleSheet.TOOL_TIP.apply(self)
        self.label.adjustSize()
        self.adjustSize()

    def _createContainer(self):
        return QFrame(self)

    def showEvent(self, e):
        self.opacityAni.setStartValue(0)
        self.opacityAni.setEndValue(1)
        self.opacityAni.start()

        self.timer.stop()
        if self.duration() > 0:
            self.timer.start(self.__duration + self.opacityAni.duration())

        super().showEvent(e)

    def hideEvent(self, e):
        self.timer.stop()
        super().hideEvent(e)

    def adjustPos(self, widget, position: ToolTipPosition):
        """ adjust the position of tooltip relative to widget """
        manager = ToolTipPositionManager.make(position)
        self.move(manager.position(self, widget))


class ToolTipPositionManager:
    """ Tooltip position manager """

    def position(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = self._pos(tooltip, parent)
        x, y = pos.x(), pos.y()

        rect = getCurrentScreenGeometry()
        x = max(rect.left(), min(pos.x(), rect.right() - tooltip.width() - 4))
        y = max(rect.top(), min(pos.y(), rect.bottom() - tooltip.height() - 4))

        return QPoint(x, y)

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        raise NotImplementedError

    @staticmethod
    def make(position: ToolTipPosition):
        """ mask info bar manager according to the display position """
        managers = {
            ToolTipPosition.TOP: TopToolTipManager,
            ToolTipPosition.BOTTOM: BottomToolTipManager,
            ToolTipPosition.LEFT: LeftToolTipManager,
            ToolTipPosition.RIGHT: RightToolTipManager,
            ToolTipPosition.TOP_RIGHT: TopRightToolTipManager,
            ToolTipPosition.BOTTOM_RIGHT: BottomRightToolTipManager,
            ToolTipPosition.TOP_LEFT: TopLeftToolTipManager,
            ToolTipPosition.BOTTOM_LEFT: BottomLeftToolTipManager,
        }

        if position not in managers:
            raise ValueError(f'`{position}` is an invalid info bar position.')

        return managers[position]()


class TopToolTipManager(ToolTipPositionManager):
    """ Top tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget):
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() + parent.width()//2 - tooltip.width()//2
        y = pos.y() - tooltip.height()
        return QPoint(x, y)


class BottomToolTipManager(ToolTipPositionManager):
    """ Bottom tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() + parent.width()//2 - tooltip.width()//2
        y = pos.y() + parent.height()
        return QPoint(x, y)


class LeftToolTipManager(ToolTipPositionManager):
    """ Left tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() - tooltip.width()
        y = pos.y() + (parent.height() - tooltip.height()) // 2
        return QPoint(x, y)


class RightToolTipManager(ToolTipPositionManager):
    """ Right tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() + parent.width()
        y = pos.y() + (parent.height() - tooltip.height()) // 2
        return QPoint(x, y)


class TopRightToolTipManager(ToolTipPositionManager):
    """ Top right tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() + parent.width() - tooltip.width() + \
            tooltip.layout().contentsMargins().right()
        y = pos.y() - tooltip.height()
        return QPoint(x, y)


class TopLeftToolTipManager(ToolTipPositionManager):
    """ Top left tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() - tooltip.layout().contentsMargins().left()
        y = pos.y() - tooltip.height()
        return QPoint(x, y)


class BottomRightToolTipManager(ToolTipPositionManager):
    """ Bottom right tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() + parent.width() - tooltip.width() + \
            tooltip.layout().contentsMargins().right()
        y = pos.y() + parent.height()
        return QPoint(x, y)


class BottomLeftToolTipManager(ToolTipPositionManager):
    """ Bottom left tooltip position manager """

    def _pos(self, tooltip: ToolTip, parent: QWidget) -> QPoint:
        pos = parent.mapToGlobal(QPoint())
        x = pos.x() - tooltip.layout().contentsMargins().left()
        y = pos.y() + parent.height()
        return QPoint(x, y)


class ItemViewToolTipManager(ToolTipPositionManager):
    """ Item view tooltip position manager """

    def __init__(self, itemRect=QRect()):
        super().__init__()
        self.itemRect = itemRect

    def _pos(self, tooltip: ToolTip, view: QAbstractItemView) -> QPoint:
        pos = view.mapToGlobal(self.itemRect.topLeft())
        x = pos.x()
        y = pos.y() - tooltip.height() + 10
        return QPoint(x, y)

    @staticmethod
    def make(tipType: ItemViewToolTipType, itemRect: QRect):
        """ mask info bar manager according to the display tipType """
        managers = {
            ItemViewToolTipType.LIST: ItemViewToolTipManager,
            ItemViewToolTipType.TABLE: TableItemToolTipManager,
        }

        if tipType not in managers:
            raise ValueError(f'`{tipType}` is an invalid info bar tipType.')

        return managers[tipType](itemRect)


class TableItemToolTipManager(ItemViewToolTipManager):
    """ Table item view tooltip position manager """

    def _pos(self, tooltip: ToolTip, view: QTableView) -> QPoint:
        pos = view.mapToGlobal(self.itemRect.topLeft())
        x = pos.x() + view.verticalHeader().isVisible() * view.verticalHeader().width()
        y = pos.y() - tooltip.height() + view.horizontalHeader().isVisible() * view.horizontalHeader().height() + 10
        return QPoint(x, y)



class ToolTipFilter(QObject):
    """ Tool button with a tool tip """

    def __init__(self, parent: QWidget, showDelay=300, position=ToolTipPosition.TOP):
        """
        Parameters
        ----------
        parent: QWidget
            the widget to install tool tip

        showDelay: int
            show tool tip after how long the mouse hovers in milliseconds

        position: TooltipPosition
            where to show the tooltip
        """
        super().__init__(parent=parent)
        self.isEnter = False
        self._tooltip = None
        self._tooltipDelay = showDelay
        self.position = position
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.showToolTip)

    def eventFilter(self, obj: QObject, e: QEvent) -> bool:
        if e.type() == QEvent.ToolTip:
            return True
        elif e.type() in [QEvent.Hide, QEvent.Leave]:
            self.hideToolTip()
        elif e.type() == QEvent.Enter:
            self.isEnter = True
            parent = self.parent()  # type: QWidget
            if self._canShowToolTip():
                if self._tooltip is None:
                    self._tooltip = self._createToolTip()

                t = parent.toolTipDuration() if parent.toolTipDuration() > 0 else -1
                self._tooltip.setDuration(t)

                # show the tool tip after delay
                self.timer.start(self._tooltipDelay)
        elif e.type() == QEvent.MouseButtonPress:
            self.hideToolTip()

        return super().eventFilter(obj, e)

    def _createToolTip(self):
        return ToolTip(self.parent().toolTip(), self.parent().window())

    def hideToolTip(self):
        """ hide tool tip """
        self.isEnter = False
        self.timer.stop()
        if self._tooltip:
            self._tooltip.hide()

    def showToolTip(self):
        """ show tool tip """
        if not self.isEnter:
            return

        parent = self.parent()  # type: QWidget
        self._tooltip.setText(parent.toolTip())
        self._tooltip.adjustPos(parent, self.position)
        self._tooltip.show()

    def setToolTipDelay(self, delay: int):
        """ set the delay of tool tip """
        self._tooltipDelay = delay

    def _canShowToolTip(self) -> bool:
        parent = self.parent()  # type: QWidget
        return parent.isWidgetType() and parent.toolTip() and parent.isEnabled()


class ItemViewToolTip(ToolTip):
    """ Item view tool tip """

    def adjustPos(self, view: QAbstractItemView, itemRect: QRect, tooltipType: ItemViewToolTipType):
        manager = ItemViewToolTipManager.make(tooltipType, itemRect)
        self.move(manager.position(self, view))



class ItemViewToolTipDelegate(ToolTipFilter):
    """ Item view tool tip """

    def __init__(self, parent: QAbstractItemView, showDelay=300, tooltipType=ItemViewToolTipType.TABLE):
        super().__init__(parent, showDelay, ToolTipPosition.TOP)
        self.text = ""
        self.currentIndex = None
        self.tooltipDuration = -1
        self.tooltipType = tooltipType
        self.viewport = parent.viewport()

        parent.installEventFilter(self)
        parent.viewport().installEventFilter(self)
        parent.horizontalScrollBar().valueChanged.connect(self.hideToolTip)
        parent.verticalScrollBar().valueChanged.connect(self.hideToolTip)

    def eventFilter(self, obj: QObject, e: QEvent) -> bool:
        if obj is self.parent():
            if e.type() in [QEvent.Type.Hide, QEvent.Type.Leave]:
                self.hideToolTip()
            elif e.type() == QEvent.Type.Enter:
                self.isEnter = True
        elif obj is self.viewport:
            if e.type() == QEvent.Type.MouseButtonPress:
                self.hideToolTip()

        return QObject.eventFilter(self, obj, e)

    def _createToolTip(self):
        return ItemViewToolTip(self.text, self.parent().window())

    def showToolTip(self):
        """ show tool tip """
        if not self._tooltip:
            self._tooltip = self._createToolTip()

        view = self.parent()  # type: QAbstractItemView
        self._tooltip.setText(self.text)

        if self.currentIndex:
            rect = view.visualRect(self.currentIndex)
        else:
            rect = QRect()

        self._tooltip.adjustPos(view, rect, self.tooltipType)
        self._tooltip.show()

    def _canShowToolTip(self) -> bool:
        return True

    def setText(self, text: str):
        self.text = text
        if self._tooltip:
            self._tooltip.setText(text)

    def setToolTipDuration(self, duration):
        self.tooltipDuration = duration
        if self._tooltip:
            self._tooltip.setDuration(duration)

    def helpEvent(self, event: QHelpEvent, view: QAbstractItemView, option: QStyleOptionViewItem, index: QModelIndex) -> bool:
        if not event or not view:
            return False

        if event.type() == QEvent.Type.ToolTip:
            text = index.data(Qt.ItemDataRole.ToolTipRole)
            if not text:
                self.hideToolTip()
                return False

            self.text = text
            self.currentIndex = index

            if not self._tooltip:
                self._tooltip = self._createToolTip()
                self._tooltip.setDuration(self.tooltipDuration)

            # show the tool tip after delay
            self.timer.start(self._tooltipDelay)

        return True
