import os
from typing import Optional

from AnyQt.QtCore import Qt, QSettings, QTimer
from AnyQt.QtWidgets import (
    QAction, QFileDialog, QMenu, QMenuBar, QWidget, QMessageBox, QDialog,
    QApplication)
from AnyQt.QtGui import QKeySequence

from orangecanvas.application.canvasmain import CanvasMainWindow
from orangewidget.report.owreport import HAVE_REPORT, OWReport
from orangewidget.workflow.widgetsscheme import WidgetsScheme


def _insert_action(mb, menuid, beforeactionid, action):
    # type: (QMenuBar, str, str, QAction) -> bool
    """
    Insert an action into one of a QMenuBar's menu.

    Parameters
    ----------
    mb : QMenuBar
        The menu bar
    menuid : str
        The target menu's objectName. The menu must be a child of `mb`.
    beforeactionid : str
        The objectName of the action before which the action will be inserted.
    action : QAction
        The action to insert

    Returns
    -------
    success: bool
        True if the actions was successfully inserted (the menu and before
        actions were found), False otherwise
    """
    def find_action(widget, name):  # type: (QWidget, str) -> Optional[QAction]
        for a in widget.actions():
            if a.objectName() == name:
                return a
        return None

    menu = mb.findChild(QMenu, menuid)
    if menu is not None:
        sep = find_action(menu, beforeactionid)
        if sep:
            menu.insertAction(sep, action)
            return True
    return False


class OWCanvasMainWindow(CanvasMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.show_report_action = QAction(
            "Show report", self,
            objectName="action-show-report",
            toolTip="Show a report window",
            shortcut=QKeySequence("Shift+R"),
            enabled=HAVE_REPORT,
        )
        self.show_report_action.triggered.connect(self.show_report_view)
        self.open_report_action = QAction(
            "Open Report...", self,
            objectName="action-open-report",
            toolTip="Open a saved report",
            enabled=HAVE_REPORT,
        )
        self.open_report_action.triggered.connect(self.open_report)
        self.reset_widget_settings_action = QAction(
            self.tr("Reset Widget Settings..."), self,
            triggered=self.reset_widget_settings
        )

        menubar = self.menuBar()
        # Insert the 'Load report' in the File menu ...
        _insert_action(menubar, "file-menu", "open-actions-separator",
                       self.open_report_action)
        # ... and 'Show report' in the View menu.
        _insert_action(menubar, "view-menu", "view-visible-actions-separator",
                       self.show_report_action)

        _insert_action(menubar, "options-menu", "canvas-addons-action",
                       self.reset_widget_settings_action)

    def open_report(self):
        """
        Present an 'Open report' dialog to the user, load a '.report' file
        (as saved by OWReport) and create a new canvas window associated
        with the OWReport instance.
        """
        settings = QSettings()
        KEY = "report/file-dialog-dir"
        start_dir = settings.value(KEY, "", type=str)
        dlg = QFileDialog(
            self,
            windowTitle=self.tr("Open Report"),
            acceptMode=QFileDialog.AcceptOpen,
            fileMode=QFileDialog.ExistingFile,
        )
        if os.path.isdir(start_dir):
            dlg.setDirectory(start_dir)

        dlg.setWindowModality(Qt.ApplicationModal)
        dlg.setNameFilters(["Report (*.report)"])

        def accepted():
            directory = dlg.directory().absolutePath()
            filename = dlg.selectedFiles()[0]
            settings.setValue(KEY, directory)
            self._open_report(filename)

        dlg.accepted.connect(accepted)
        dlg.exec()

    def _open_report(self, filename):
        """
        Open and load a '*.report' from 'filename'
        """
        report = OWReport.load(filename)
        # Create a new window for the report
        if self.is_transient():
            window = self
        else:
            window = self.create_new_window()
        # toggle the window modified flag (this will clear the 'is_transient'
        # flag on the new window)
        window.setWindowModified(True)
        window.setWindowModified(False)

        report.setParent(window, Qt.Window)
        sc = window.current_document().scheme()  # type: WidgetsScheme
        sc.set_report_view(report)

        window.show()
        window.raise_()
        window.show_report_view()

        report._build_html()
        report.table.selectRow(0)
        report.show()
        report.raise_()

    def show_report_view(self):
        """
        Show the 'Report' view for the current workflow.
        """
        sc = self.current_document().scheme()  # type: WidgetsScheme
        sc.show_report_view()

    def reset_widget_settings(self):
        name = QApplication.applicationName() or 'Orange'
        mb = QMessageBox(
            self,
            windowTitle="Clear settings",
            text=f"{name} needs to be restarted for the changes to take effect.",
            icon=QMessageBox.Information,
            informativeText=f"Press OK to restart {name} now.",
            standardButtons=QMessageBox.Ok | QMessageBox.Cancel,
        )
        res = mb.exec()
        if res == QMessageBox.Ok:
            # Touch a finely crafted file inside the settings directory.
            # The existence of this file is checked by the canvas main
            # function and is deleted there.
            from orangewidget.settings import widget_settings_dir
            dirname = widget_settings_dir()
            try:
                os.makedirs(dirname, exist_ok=True)
            except (FileExistsError, PermissionError):
                return
            with open(os.path.join(dirname, "DELETE_ON_START"), "a"):
                pass

            def restart():
                quit_temp_val = QApplication.quitOnLastWindowClosed()
                QApplication.setQuitOnLastWindowClosed(False)
                QApplication.closeAllWindows()
                windows = QApplication.topLevelWindows()
                if any(w.isVisible() for w in windows):  # if a window close was cancelled
                    QApplication.setQuitOnLastWindowClosed(quit_temp_val)
                    QMessageBox(
                        text="Restart Cancelled",
                        informativeText=f"Settings will be reset on {name}'s next restart",
                        icon=QMessageBox.Information
                    ).exec()
                else:
                    QApplication.exit(96)

            QTimer.singleShot(0, restart)

    def ask_save_report(self):
        """
        Ask whether to save the report or not.
        Returns:
            `QDialog.Rejected` if user cancels, `QDialog.Accepted` otherwise
        """
        workflow = self.current_document().scheme()
        if not isinstance(workflow, WidgetsScheme) or not workflow.has_report():
            return QDialog.Accepted

        report = workflow.report_view()
        if not report.is_changed():
            return QDialog.Accepted

        mBox = QMessageBox(
            self,
            windowTitle="Report window",
            icon=QMessageBox.Question,
            text="The report contains unsaved changes.",
            informativeText="Would you like to save the report?",
            standardButtons=QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel,
        )
        mBox.setDefaultButton(QMessageBox.Save)
        answ = mBox.exec()
        if answ == QMessageBox.Cancel:
            return QDialog.Rejected
        if answ == QMessageBox.Save:
            return report.save_report()
        return QDialog.Accepted

    def closeEvent(self, event):
        if self.ask_save_report() == QDialog.Rejected:
            event.ignore()
            return
        super().closeEvent(event)
