"""
Generic custom widgets.

Copyright (C) 2018  The Freedom of the Press Foundation.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QWidget

from securedrop_client.resources import load_icon, load_svg


class SvgToggleButton(QPushButton):
    """
    A toggle button used to display the contents of Scalable Vector Graphics (SVG) files provided
    for an on and off state.

    Parameters
    ----------
    on: str
        The name of the SVG file to add to the button for on state.
    off: str
        The name of the SVG file to add to the button for off state.
    svg_size: QSize, optional
        The display size of the SVG, defaults to filling the entire size of the widget.
    """

    def __init__(self, on: str, off: str, svg_size: QSize | None = None):
        super().__init__()

        # Set layout
        layout = QHBoxLayout(self)
        self.setLayout(layout)

        # Remove margins and spacing
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        # Add SVG icon and set its size
        self._icon = load_icon(normal=on, normal_off=off)
        self.setIcon(self._icon)
        self.setIconSize(svg_size) if svg_size else self.setIconSize(QSize())

        # Make this a toggle button
        self.setCheckable(True)

    def set_icon(self, on: str, off: str) -> None:
        self._icon = load_icon(normal=on, normal_off=off)
        self.setIcon(self._icon)


class SvgPushButton(QPushButton):
    """
    A widget used to display the contents of Scalable Vector Graphics (SVG) files provided for
    associated user action modes, see https://doc.qt.io/qt-5/qicon.html#Mode-enum.

    Parameters
    ----------
    normal: str
        The name of the SVG file to add to the button for QIcon.Normal mode.
    disabled: str, optional
        The name of the SVG file to add to the button for QIcon.Disabled mode.
    active: str, optional
        The name of the SVG file to add to the button for QIcon.Active mode.
    selected: str, optional
        The name of the SVG file to add to the button for QIcon.Selected mode.
    svg_size: QSize, optional
        The display size of the SVG, defaults to filling the entire size of the widget.
    """

    def __init__(
        self,
        normal: str,
        disabled: str | None = None,
        active: str | None = None,
        selected: str | None = None,
        svg_size: QSize | None = None,
    ) -> None:
        super().__init__()

        # Set layout
        layout = QHBoxLayout(self)
        self.setLayout(layout)

        # Remove margins and spacing
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        # Add SVG icon and set its size
        self._icon: QIcon = load_icon(
            normal=normal,
            disabled=disabled,
            active=active,
            selected=selected,
            disabled_off=disabled,
        )
        self.setIcon(self._icon)
        self.setIconSize(svg_size) if svg_size else self.setIconSize(QSize())


class SvgLabel(QLabel):
    """
    A widget used to display the contents of a Scalable Vector Graphics (SVG) file.

    Parameters
    ----------
    filename: str
        The name of the SVG file to add to the label.
    svg_size: QSize, optional
        The display size of the SVG, defaults to filling the entire size of the widget.
    """

    def __init__(self, filename: str, svg_size: QSize | None = None) -> None:
        super().__init__()

        # Remove margins and spacing
        layout = QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setLayout(layout)

        # Add SVG and set its size
        self.svg = load_svg(filename)
        self.svg.setFixedSize(svg_size) if svg_size else self.svg.setFixedSize(QSize())
        layout.addWidget(self.svg)

    def update_image(self, filename: str, svg_size: QSize | None = None) -> None:
        self.svg = load_svg(filename)
        self.svg.setFixedSize(svg_size) if svg_size else self.svg.setFixedSize(QSize())
        child = self.layout().takeAt(0)
        if child and child.widget():
            child.widget().deleteLater()
        self.layout().addWidget(self.svg)


class SecureQLabel(QLabel):
    MAX_PREVIEW_LENGTH = 200

    def __init__(
        self,
        text: str = "",
        parent: QWidget | None = None,
        flags: Qt.WindowFlags | Qt.WindowType = Qt.WindowFlags(),
        wordwrap: bool = True,
        max_length: int = 0,
        with_tooltip: bool = False,
    ):
        super().__init__(parent, flags)
        self.wordwrap = wordwrap
        self.max_length = max_length
        self.setWordWrap(wordwrap)  # If True, wraps text at default of 70 characters
        self.with_tooltip = with_tooltip
        self.setText(text)
        self.elided = self.text() != text

    def setText(self, text: str) -> None:
        text = text.strip()
        self.setTextFormat(Qt.PlainText)
        self.preview_text = text[: self.MAX_PREVIEW_LENGTH]
        elided_text = self.get_elided_text(text)
        self.elided = elided_text != text
        if self.elided and self.with_tooltip:
            tooltip_label = SecureQLabel(text)
            self.setToolTip(tooltip_label.text())
        super().setText(elided_text)

    def refresh_preview_text(self) -> None:
        self.setText(self.preview_text)

    def get_elided_text(self, full_text: str) -> str:
        if not self.max_length:
            return full_text

        # Only allow one line of elided text
        if "\n" in full_text:
            full_text = full_text.split("\n", 1)[0]

        fm = self.fontMetrics()
        px_width = fm.horizontalAdvance(full_text)
        if px_width > self.max_length:
            elided_text = ""
            for c in full_text:
                if fm.horizontalAdvance(elided_text) > self.max_length:
                    return elided_text[:-3] + "…"
                elided_text = elided_text + c

        return full_text

    def is_elided(self) -> bool:
        return self.elided
