from abc import abstractmethod
from typing import Optional

from PyQt5.QtCore import QParallelAnimationGroup, Qt, QPropertyAnimation, QAbstractAnimation, QSize
from PyQt5.QtWidgets import QWidget, QFrame, QToolButton, QGridLayout, QSizePolicy, QGroupBox, QLabel

from rare.utils.misc import icon


class CollapsibleBase(object):
    """
    References:
        # Adapted from c++ version
        https://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt
        # Adapted from python version
        https://newbedev.com/how-to-make-an-expandable-collapsable-section-widget-in-qt
    """
    def __init__(self):
        self.animation_duration = None
        self.toggle_animation = None
        self.content_area = None
        self.content_toggle_animation = None

    def setup(self, animation_duration: int = 200):
        self.animation_duration = animation_duration
        self.content_area: Optional[QWidget] = None
        self.content_toggle_animation: Optional[QPropertyAnimation] = None

        # let the entire widget grow and shrink with its content
        self.toggle_animation = QParallelAnimationGroup(self)
        self.toggle_animation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
        self.toggle_animation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))

    @abstractmethod
    def isChecked(self) -> bool:
        ...

    @abstractmethod
    def click(self) -> None:
        ...

    @abstractmethod
    def addToLayout(self, widget: QWidget) -> None:
        ...

    @abstractmethod
    def sizeHint(self) -> QSize:
        ...

    def animationStart(self, checked):
        direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward
        self.toggle_animation.setDirection(direction)
        self.toggle_animation.start()

    def setWidget(self, widget: QWidget):
        if widget is None or widget is self.content_area:
            return
        is_checked = self.isChecked()
        if self.content_area is not None:
            # Collapse the parent before replacing the child
            if is_checked:
                self.click()
            self.toggle_animation.removeAnimation(self.content_toggle_animation)
            self.content_area.setParent(None)
            self.content_area.deleteLater()

        self.content_area = widget
        self.content_area.setParent(self)
        self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        # start out collapsed
        if not is_checked:
            self.content_area.setMaximumHeight(0)
            self.content_area.setMinimumHeight(0)

        self.content_toggle_animation = QPropertyAnimation(self.content_area, b"maximumHeight")
        self.toggle_animation.addAnimation(self.content_toggle_animation)
        self.addToLayout(self.content_area)
        collapsed_height = self.sizeHint().height()
        content_height = self.content_area.sizeHint().height()
        for i in range(self.toggle_animation.animationCount() - 1):
            spoiler_animation = self.toggle_animation.animationAt(i)
            spoiler_animation.setDuration(self.animation_duration)
            spoiler_animation.setStartValue(collapsed_height)
            spoiler_animation.setEndValue(collapsed_height + content_height)
        content_animation = self.toggle_animation.animationAt(self.toggle_animation.animationCount() - 1)
        content_animation.setDuration(self.animation_duration)
        content_animation.setStartValue(0)
        content_animation.setEndValue(content_height)
        if is_checked:
            self.click()


class CollapsibleFrame(QFrame, CollapsibleBase):
    def __init__(
        self, widget: QWidget = None, title: str = "", button_text: str = "", animation_duration: int = 200, parent=None
    ):
        super(CollapsibleFrame, self).__init__(parent=parent)
        self.setup(animation_duration)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)

        self.toggle_button = QToolButton(self)
        self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.toggle_button.setIcon(icon("fa.arrow-right"))
        self.toggle_button.setText(button_text)
        self.toggle_button.setCheckable(True)
        self.toggle_button.setChecked(False)

        self.title_label = QLabel(title)
        font = self.title_label.font()
        font.setBold(True)
        self.title_label.setFont(font)
        self.title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        # don't waste space
        self.main_layout = QGridLayout(self)
        self.main_layout.setVerticalSpacing(0)
        self.main_layout.setContentsMargins(0, 0, 0, 0)
        self.main_layout.addWidget(self.toggle_button, 0, 0, 1, 1, Qt.AlignLeft)
        self.main_layout.addWidget(self.title_label, 0, 1, 1, 1)
        self.main_layout.setColumnStretch(1, 1)
        self.main_layout.setRowStretch(0, 0)
        self.main_layout.setRowStretch(1, 1)
        self.setLayout(self.main_layout)

        self.toggle_button.clicked.connect(self.animationStart)

        if widget is not None:
            self.setWidget(widget)

    def isChecked(self) -> bool:
        return self.toggle_button.isChecked()

    def click(self) -> None:
        self.toggle_button.click()

    def addToLayout(self, widget: QWidget) -> None:
        self.main_layout.addWidget(widget, 1, 0, 1, 2)

    def sizeHint(self) -> QSize:
        return super(CollapsibleFrame, self).sizeHint()

    def animationStart(self, checked):
        arrow_type = icon("fa.arrow-down") if checked else icon("fa.arrow-right")
        self.toggle_button.setIcon(arrow_type)
        super(CollapsibleFrame, self).animationStart(checked)


class CollapsibleGroupBox(QGroupBox, CollapsibleBase):
    def __init__(
        self, widget: QWidget = None, title: str = "", animation_duration: int = 200, parent=None
    ):
        super(CollapsibleGroupBox, self).__init__(parent=parent)
        self.setup(animation_duration)
        self.setTitle(title)
        self.setCheckable(True)
        self.setChecked(False)

        # don't waste space
        self.main_layout = QVBoxLayout(self)
        self.main_layout.setSpacing(0)
        self.main_layout.setContentsMargins(0, 0, 0, -1)
        self.setLayout(self.main_layout)

        self.toggled.connect(self.animationStart)

        if widget is not None:
            self.setWidget(widget)

    def isChecked(self) -> bool:
        return super(CollapsibleGroupBox, self).isChecked()

    def click(self) -> None:
        self.setChecked(not self.isChecked())

    def addToLayout(self, widget: QWidget) -> None:
        self.main_layout.addWidget(widget)

    def sizeHint(self) -> QSize:
        return super(CollapsibleGroupBox, self).sizeHint()


if __name__ == "__main__":
    import sys
    from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
    from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
    from rare.utils.misc import set_style_sheet
    from rare.resources.stylesheets import RareStyle
    app = QApplication(sys.argv)

    set_style_sheet("RareStyle")

    ui_frame = Ui_InstallDialogAdvanced()
    widget_frame = QWidget()
    ui_frame.setupUi(widget_frame)
    collapsible_frame = CollapsibleFrame(widget_frame, title="Frame me!")
    collapsible_frame.setDisabled(False)

    def replace_func_frame(state):
        widget2_frame = QWidget()
        ui2_frame = Ui_InstallDialogAdvanced()
        ui2_frame.setupUi(widget2_frame)
        if state:
            ui2_frame.install_dialog_advanced_layout.removeRow(3)
            ui2_frame.install_dialog_advanced_layout.removeRow(4)
        collapsible_frame.setWidget(widget2_frame)

    ui_group = Ui_InstallDialogAdvanced()
    widget_group = QWidget()
    ui_group.setupUi(widget_group)
    collapsible_group = CollapsibleGroupBox(widget_group, title="Group me!")
    collapsible_group.setDisabled(False)

    def replace_func_group(state):
        widget2_group = QWidget()
        ui2_group = Ui_InstallDialogAdvanced()
        ui2_group.setupUi(widget2_group)
        if state:
            ui2_group.install_dialog_advanced_layout.removeRow(3)
            ui2_group.install_dialog_advanced_layout.removeRow(4)
        collapsible_group.setWidget(widget2_group)

    replace_button = QToolButton()
    replace_button.setText("Replace me!")
    replace_button.setCheckable(True)
    replace_button.setChecked(False)
    replace_button.clicked.connect(replace_func_frame)
    replace_button.clicked.connect(replace_func_group)

    dialog = QDialog()
    dialog.setLayout(QVBoxLayout())
    dialog.layout().addWidget(replace_button)
    dialog.layout().addWidget(collapsible_frame)
    dialog.layout().addWidget(collapsible_group)
    dialog.layout().setSizeConstraint(QVBoxLayout.SetFixedSize)
    dialog.show()
    sys.exit(app.exec_())

