# -*- coding: utf-8 -*-

# Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a widget showing the list of open pages.
"""

from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint
from PyQt6.QtGui import QGuiApplication, QClipboard
from PyQt6.QtWidgets import QListWidget, QAbstractItemView, QMenu

import UI.PixmapCache


class OpenPagesWidget(QListWidget):
    """
    Class implementing a widget showing the list of open pages.
    
    @signal currentPageChanged(index) emitted to signal a change of the current
        page index
    """
    currentPageChanged = pyqtSignal(int)
    
    def __init__(self, stack, parent=None):
        """
        Constructor
        
        @param stack reference to the stack widget containing the open
            help pages
        @type QStackedWidget
        @param parent reference to the parent widget (defaults to None)
        @type QWidget (optional)
        """
        super().__init__(parent)
        self.setObjectName("OpenPagesWidget")
        
        self.__helpViewer = parent
        
        self.setAlternatingRowColors(True)
        self.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection)
        self.setContextMenuPolicy(
            Qt.ContextMenuPolicy.CustomContextMenu)
        self.currentRowChanged.connect(
            self.__currentRowChanged)
        self.customContextMenuRequested.connect(
            self.__showContextMenu)
        
        self.__stack = stack
        self.__stack.currentChanged.connect(self.__currentPageChanged)
        
        self.__initContextMenu()
        
        self.__defaultFont = self.font()
        self.__boldFont = self.font()
        self.__boldFont.setBold(True)
    
    def __initContextMenu(self):
        """
        Private method to initialize the context menu.
        """
        self.__menu = QMenu(self)
        self.__menu.addAction(
            UI.PixmapCache.getIcon("tabClose"),
            self.tr('Close'), self.__contextMenuClose)
        self.closeOthersMenuAct = self.__menu.addAction(
            UI.PixmapCache.getIcon("tabCloseOther"),
            self.tr("Close Others"),
            self.__contextMenuCloseOthers)
        self.__menu.addAction(
            self.tr('Close All'), self.__contextMenuCloseAll)
        self.__menu.addSeparator()
        self.__copyUrlAct = self.__menu.addAction(
            self.tr("Copy URL to Clipboard"),
            self.__contextMenuCopyUrlToClipboard)
    
    @pyqtSlot(QPoint)
    def __showContextMenu(self, point):
        """
        Private slot to handle the customContextMenuRequested signal of
        the viewlist.
        
        @param point position to open the menu at
        @type QPoint
        """
        itm = self.itemAt(point)
        self.__copyUrlAct.setEnabled(bool(itm) and itm.text() != "about:blank")
        self.closeOthersMenuAct.setEnabled(self.count() > 1)
        self.__menu.popup(self.mapToGlobal(point))
    
    @pyqtSlot(int)
    def __currentPageChanged(self, index):
        """
        Private slot to handle a change of the shown page.
        
        @param index index of the current page
        @type int
        """
        for row in range(self.count()):
            itm = self.item(row)
            itm.setFont(
                self.__boldFont if row == index else self.__defaultFont
            )
    
    @pyqtSlot(int)
    def __currentRowChanged(self, row):
        """
        Private slot handling a change of the current row.
        
        @param row current row
        @type int
        """
        self.__stack.setCurrentIndex(row)
        self.currentPageChanged.emit(row)
    
    def addPage(self, viewer, background=False):
        """
        Public method to add a viewer page to our list.
        
        @param viewer reference to the viewer object
        @type HelpViewerImpl
        @param background flag indicating to not change the current page
            (defaults to False)
        @type bool (optional)
        """
        self.addItem(viewer.pageTitle())
        viewer.titleChanged.connect(
            lambda: self.__viewerTitleChanged(viewer))
        
        if not background:
            self.setCurrentRow(
                self.count() - 1)
        if self.count() == 1:
            self.__currentPageChanged(0)
    
    def insertPage(self, index, viewer, background=False):
        """
        Public method to insert a viewer page into our list.
        
        @param index index to insert at
        @type int
        @param viewer reference to the viewer object
        @type HelpViewerImpl
        @param background flag indicating to not change the current page
            (defaults to False)
        @type bool (optional)
        """
        currentRow = self.currentRow()
        self.insertItem(index, viewer.pageTitle())
        viewer.titleChanged.connect(
            lambda: self.__viewerTitleChanged(viewer))
        
        if not background:
            self.setCurrentRow(index)
        else:
            self.setCurrentRow(currentRow)
    
    def __viewerTitleChanged(self, viewer):
        """
        Private method to handle the change of a viewer title.
        
        @param viewer reference to the viewer that change title
        @type HelpViewerImpl
        """
        index = self.__stack.indexOf(viewer)
        itm = self.item(index)
        itm.setText(viewer.pageTitle())
        self.currentPageChanged.emit(index)
    
    #######################################################################
    ## Context menu action methods
    #######################################################################
    
    @pyqtSlot()
    def __contextMenuClose(self):
        """
        Private slot to close a page via the context menu.
        """
        self.closeCurrentPage()
    
    @pyqtSlot()
    def __contextMenuCloseOthers(self):
        """
        Private slot to close all other pages via the context menu.
        """
        self.closeOtherPages()
    
    @pyqtSlot()
    def __contextMenuCloseAll(self):
        """
        Private slot to close all pages via the context menu.
        """
        self.closeAllPages()
    
    @pyqtSlot()
    def __contextMenuCopyUrlToClipboard(self):
        """
        Private slot to copy the URL to the clipboard.
        """
        row = self.currentRow()
        viewer = self.__stack.widget(row)
        url = viewer.link()
        if url.isValid():
            urlStr = url.toString()
            
            # copy the URL to both clipboard areas
            QGuiApplication.clipboard().setText(
                urlStr, QClipboard.Mode.Clipboard)
            QGuiApplication.clipboard().setText(
                urlStr, QClipboard.Mode.Selection)
    
    def __removeViewer(self, row):
        """
        Private method to remove a viewer page.
        
        @param row row associated with the viewer
        @type int
        """
        viewer = self.__stack.widget(row)
        self.__stack.removeWidget(viewer)
        viewer.deleteLater()
        
        itm = self.takeItem(row)
        del itm
    
    #######################################################################
    ## Slots for external access below
    #######################################################################
    
    @pyqtSlot()
    def closeCurrentPage(self):
        """
        Public slot to close the current page.
        """
        row = self.currentRow()
        self.__removeViewer(row)
        
        if self.count() == 0:
            self.__helpViewer.addPage()
    
    @pyqtSlot()
    def closeOtherPages(self):
        """
        Public slot to close all other pages.
        """
        currentRow = self.currentRow()
        for row in range(self.count() - 1, -1, -1):
            if row != currentRow:
                self.__removeViewer(row)
    
    @pyqtSlot()
    def closeAllPages(self):
        """
        Public slot to close all pages.
        """
        while self.count() != 0:
            self.__removeViewer(0)
        self.__helpViewer.addPage()
