from __future__ import annotations

import os
import pathlib
import re
from pathlib import Path

from numpy import geomspace, linspace
from pyqtgraph import ViewBox

from nmreval.configs import *

from .management import UpperManagement
from ..Qt import QtCore, QtGui, QtPrintSupport, QtWidgets
from ..data.shift_graphs import QShift
from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog
from ..fit.result import QFitResult
from ..graphs.graphwindow import QGraphWindow
from ..graphs.movedialog import QMover
from ..io.fcbatchreader import QFCReader
from ..io.filedialog import *
from ..lib import get_icon, make_action_icons
from ..lib.pg_objects import RegionItem
from ..math.evaluation import QEvalDialog
from ..math.interpol import InterpolDialog
from ..math.mean_dialog import QMeanTimes
from ..math.smooth import QSmooth
from ..nmr.coupling_calc import QCoupCalcDialog
from ..nmr.t1_from_tau import QRelaxCalc
from .._py.basewindow import Ui_BaseWindow
from ..lib.utils import UpdateDialog, open_bug_report, Updater


class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
    closeSignal = QtCore.pyqtSignal()
    openpoints = QtCore.pyqtSignal(dict)
    save_ses_sig = QtCore.pyqtSignal(str)
    rest_ses_sig = QtCore.pyqtSignal(str)

    def __init__(self, parents=None, path=None):
        super().__init__(parent=parents)

        if path is None:
            self.path = Path.home()
        else:
            self.path = Path(path)

        self.read_state()

        self.management = UpperManagement(self)

        self.fitlimitvalues = [None, None]
        self.fitpreview = []
        self._fit_plot_id = None
        self.savefitdialog = None
        self.eval = None
        self.editor = None

        self.movedialog = QMover(self)

        self.current_graph_widget = None
        self.current_plotitem = None
        self._block_window_change = False

        self.fname = None
        self.tim = QtCore.QTimer()

        self.settings = QtCore.QSettings('NMREVal', 'settings')
        self._init_gui()
        self._init_signals()

        if os.getenv('APPIMAGE') is not None:
            if Updater.get_update_information(os.getenv('APPIMAGE'))[0]:
                self.look_for_update()

    def _init_gui(self):
        self.setupUi(self)
        make_action_icons(self)
        self.setWindowIcon(get_icon('logo'))

        self.norm_toolbutton = QtWidgets.QToolButton(self)
        self.norm_toolbutton.setMenu(self.menuNormalize)
        self.norm_toolbutton.setPopupMode(self.norm_toolbutton.InstantPopup)
        self.norm_toolbutton.setIcon(get_icon('normal'))
        self.toolbar_edit.addWidget(self.norm_toolbutton)

        self.fitlim_button = QtWidgets.QToolButton(self)
        self.fitlim_button.setMenu(self.menuLimits)
        self.fitlim_button.setPopupMode(self.fitlim_button.InstantPopup)
        self.fitlim_button.setIcon(get_icon('fit_region'))
        self.toolBar_fit.addWidget(self.fitlim_button)

        self.area.dragEnterEvent = self.dragEnterEvent

        while self.tabWidget.count() > 2:
            self.tabWidget.removeTab(self.tabWidget.count()-1)

        # Prevent closing "data" and "values"
        for i in [0, 1]:
            self.tabWidget.tabBar().tabButton(i, QtWidgets.QTabBar.ButtonPosition.RightSide).resize(0, 0)

        self.setAcceptDrops(True)

        self.mousepos = QtWidgets.QLabel('')
        self.status = QtWidgets.QLabel('')
        self.statusBar.addWidget(self.status)
        self.statusBar.addWidget(self.mousepos)

        self.fitregion = RegionItem()
        self._fit_plot_id = None

        self.setGeometry(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter,
                                                      self.size(), QtWidgets.qApp.desktop().availableGeometry()))

        self.datawidget.management = self.management
        self.integralwidget.management = self.management
        self.drawingswidget.graphs = self.management.graphs

        self.ac_group = QtWidgets.QActionGroup(self)
        self.ac_group.addAction(self.action_lm_fit)
        self.ac_group.addAction(self.action_nm_fit)
        self.ac_group.addAction(self.action_odr_fit)

        self.ac_group2 = QtWidgets.QActionGroup(self)
        self.ac_group2.addAction(self.action_no_range)
        self.ac_group2.addAction(self.action_x_range)
        self.ac_group2.addAction(self.action_custom_range)

    def _init_signals(self):
        self.actionRedo = self.management.undostack.createRedoAction(self)
        icon = QtGui.QIcon.fromTheme("edit-redo")
        self.actionRedo.setIcon(icon)
        self.actionRedo.setShortcuts(QtGui.QKeySequence.Redo)
        self.menuData.insertAction(self.action_new_set, self.actionRedo)

        self.actionUndo = self.management.undostack.createUndoAction(self)
        self.actionUndo.setShortcuts(QtGui.QKeySequence.Undo)
        icon = QtGui.QIcon.fromTheme("edit-undo")
        self.actionUndo.setIcon(icon)
        self.menuData.insertAction(self.actionRedo, self.actionUndo)

        # # self.actionSave.triggered.connect(lambda: self.management.save('/autohome/dominik/nmreval/testdata/test.nmr', ''))
        # self.actionSave.triggered.connect(self.save)
        self.action_save_fit_parameter.triggered.connect(self.save_fit_parameter)
        self.ac_group2.triggered.connect(self.change_fit_limits)

        self.t1action.triggered.connect(lambda: self._show_tab('t1_temp'))
        self.action_edit.triggered.connect(lambda: self._show_tab('signal'))
        self.actionPick_position.triggered.connect(lambda: self._show_tab('pick'))
        self.actionIntegration.triggered.connect(lambda: self._show_tab('integrate'))
        self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit'))
        self.action_draw_object.triggered.connect(lambda: self._show_tab('drawing'))

        self.action_new_set.triggered.connect(self.management.create_empty)

        self.datawidget.keyChanged.connect(self.management.change_keys)
        self.datawidget.tree.deleteItem.connect(self.management.delete_sets)
        self.datawidget.tree.moveItem.connect(self.management.move_sets)
        self.datawidget.tree.copyItem.connect(self.management.copy_sets)
        self.datawidget.graph_toolButton.clicked.connect(self.new_graph)
        self.datawidget.empty_toolButton.clicked.connect(self.management.create_empty)
        self.datawidget.func_toolButton.clicked.connect(self.make_data_from_function)
        self.datawidget.tree.stateChanged.connect(self.management.change_visibility)
        self.datawidget.startShowProperty.connect(self.management.get_properties)
        self.datawidget.propertyChanged.connect(self.management.update_property)
        self.datawidget.tree.saveFits.connect(self.save_fit_parameter)

        self.management.newData.connect(self.show_new_data)
        self.management.newGraph.connect(self.new_graph)
        self.management.dataChanged.connect(self.update_data)
        self.management.deleteData.connect(self.delete_data)
        self.management.deleteGraph.connect(self.remove_graph)
        self.management.restoreGraph.connect(self.set_graph)
        self.management.properties_collected.connect(self.datawidget.set_properties)
        self.management.unset_state.connect(lambda x: self.datawidget.uncheck_sets(x))
        self.management.fitFinished.connect(self.show_fit_results)

        self.fit_dialog._management = self.management
        self.fit_dialog.preview_emit.connect(self.show_fit_preview)
        self.fit_dialog.fitStartSig.connect(self.start_fit)
        self.fit_dialog.abortFit.connect(lambda : self.management.stopFit.emit())

        self.movedialog.moveData.connect(self.move_sets)
        self.movedialog.copyData.connect(self.management.copy_sets)

        self.ptsselectwidget.points_selected.connect(self.management.extract_points)

        self.t1tauwidget.newData.connect(self.management.add_new_data)

        self.editsignalwidget.do_something.connect(self.management.apply)
        self.editsignalwidget.preview_triggered.connect(self.do_preview)

        self.action_sort_pts.triggered.connect(lambda: self.management.apply('sort', ()))
        self.action_calc_eps_derivative.triggered.connect(self.management.bds_deriv)
        self.action_magnitude.triggered.connect(self.management.calc_magn)
        self.actionCenterMax.triggered.connect(lambda: self.management.apply('center', ()))

        self.valuewidget.requestData.connect(self.show_data_values)
        self.valuewidget.itemChanged.connect(self.management.set_values)
        self.valuewidget.itemDeleted.connect(self.management.remove_values)
        self.valuewidget.itemAdded.connect(self.management.append)
        self.valuewidget.maskSignal.connect(self.management.mask_value)
        self.valuewidget.values_selected.connect(self.plot_selected_values)
        self.valuewidget.split_signal.connect(self.management.split_set)

        self.actionMaximize.triggered.connect(lambda: self.current_graph_widget.showMaximized())
        self.actionNext_window.triggered.connect(lambda: self.area.activateNextSubWindow())
        self.actionPrevious.triggered.connect(lambda: self.area.activatePreviousSubWindow())

        self.closeSignal.connect(self.close)

        self.action_norm_max.triggered.connect(lambda: self.management.apply('norm', ('max',)))
        self.action_norm_max_abs.triggered.connect(lambda: self.management.apply('norm', ('maxabs',)))
        self.action_norm_first.triggered.connect(lambda: self.management.apply('norm', ('first',)))
        self.action_norm_last.triggered.connect(lambda: self.management.apply('norm', ('last',)))
        self.action_norm_area.triggered.connect(lambda: self.management.apply('norm', ('area',)))
        self.action_cut.triggered.connect(lambda: self.management.cut())

        self.actionConcatenate_sets.triggered.connect(lambda : self.management.cat())

    @QtCore.pyqtSlot(name='on_action_open_triggered')
    def open(self):
        filedialog = OpenFileDialog(directory=self.path, caption='Open files',
                                    filter='All files (*.*);;'
                                           'Program session (*.nmr);;'
                                           'HDF files (*.h5);;'
                                           'Text files (*.txt *.dat);;'
                                           'Novocontrol Alpha (*.EPS);;'
                                           'TecMag files (*.tnt);;'
                                           'Grace files (*.agr)')

        filedialog.set_graphs(self.management.graphs.list())

        filedialog.exec()
        fname = filedialog.selectedFiles()

        if fname:
            self.path = Path(fname[0]).parent
            self.management.load_files(fname, new_plot=filedialog.add_to_graph)

    @QtCore.pyqtSlot(name='on_actionOpen_FC_triggered')
    def read_fc(self):
        reader = QFCReader(path=self.path, parent=self)
        reader.data_read.connect(self.management.add_new_data)
        reader.exec()

        del reader

    @QtCore.pyqtSlot(name='on_actionPrint_triggered')
    def print(self):
        QtPrintSupport.QPrintDialog().exec()

    @QtCore.pyqtSlot(name='on_actionExportData_triggered')
    @QtCore.pyqtSlot(name='on_actionSave_triggered')
    def save(self):
        save_dialog = SaveDirectoryDialog(directory=str(self.path), parent=self)

        mode = save_dialog.exec()
        if mode == QtWidgets.QDialog.Accepted:
            savefile = save_dialog.save_file()
            selected_filter = save_dialog.selectedNameFilter()

            if savefile is not None:
                use_underscore = save_dialog.checkBox.isChecked()
                self.management.save(savefile, selected_filter, strip_spaces=use_underscore)

                param_outfile = re.sub('[_\s-]?<label>[_\s-]?', '', savefile.stem)

                bad_character = r'/*<>\|:"'
                for c in bad_character:
                    param_outfile = param_outfile.replace(c, '')

                if save_dialog.agr_cb.isChecked():
                    self.current_graph_widget.export(savefile.with_name(param_outfile + '.agr'))

                if save_dialog.fit_cb.isChecked():
                    self.management.save_fit_parameter(savefile.with_name(param_outfile + '.dat'))

    @QtCore.pyqtSlot()
    @QtCore.pyqtSlot(list)
    def save_fit_parameter(self, fit_sets: list[str] = None):
        save_dialog = FileDialog(parent=self, caption='Save fit parameter', directory=str(self.path),
                                 filter='All files(*, *);;Text files(*.dat *.txt)')
        save_dialog.setAcceptMode(FileDialog.AcceptSave)
        save_dialog.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, True)

        mode = save_dialog.exec()
        if mode == QtWidgets.QDialog.Accepted:
            savefile = save_dialog.save_file()
            if savefile:
                self.management.save_fit_parameter(savefile, fit_sets=fit_sets)

    @QtCore.pyqtSlot(name='on_actionExportGraphic_triggered')
    def export_graphic(self):
        self.current_graph_widget.export_dialog()

    @QtCore.pyqtSlot(name='on_actionNew_window_triggered')
    def new_graph(self):
        w = QGraphWindow()
        self.management.graphs[w.id] = w
        self.set_graph(w.id)

        if self.eval is not None:
            self.eval.set_graphs(self.management.graphs.list())

        return w.id

    @QtCore.pyqtSlot(list, str)
    def show_new_data(self, sets: list, graph: str):
        if len(sets) == 0:
            return

        if graph == '':
            graph = self.new_graph()

        self.management.plots_to_graph(sets, graph)

        for idd in sets:
            new_item = self.management[idd]
            self.datawidget.blockSignals(True)
            self.datawidget.add_item(new_item.id, new_item.name, graph)
            self.datawidget.blockSignals(False)

        if graph == self.fit_dialog.connected_figure:
            self.fit_dialog.load(self.management.graphs.active(graph))

        if self.valuewidget.isVisible():
            self.valuewidget(self.management.graphs.tree())

    @QtCore.pyqtSlot(name='on_actionDelete_window_triggered')
    def delete_windows(self):
        self.management.delete_sets()

    @QtCore.pyqtSlot(str)
    def remove_graph(self, gid: str):
        self.datawidget.remove_item(gid)
        val_figure = self.valuewidget.connected_figure
        self.valuewidget.remove_graph()

        w = None
        for w in self.area.subWindowList():
            wdgt = w.widget()
            if wdgt.id == gid:
                w.disconnect()
                wdgt.disconnect()
                wdgt.scene.disconnect()
                if wdgt == self.current_graph_widget:
                    if self.ptsselectwidget.connected_figure == gid:
                        self.ptsselectwidget.connected_figure = None
                        self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget))

                    if self.t1tauwidget.connected_figure == gid:
                        self.t1tauwidget.connected_figure = None
                        self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget))

                    if self.fit_dialog.connected_figure == gid:
                        self.fit_dialog.connected_figure = None
                        for item in self.fit_dialog.preview_lines:
                            self.current_graph_widget.remove_external(item)

                    if val_figure == gid:
                        self.tabWidget.setCurrentIndex(0)

                    self.current_graph_widget.enable_picking(False)

                    self.current_graph_widget = None
                    self.management.current_graph = ''
                    self.current_plotitem = None

                wdgt.setParent(None)
                try:
                    wdgt.deleteLater()
                except AttributeError:
                    pass

                break


        if w is not None:
            self.area.removeSubWindow(w)
            w.close()
            try:
                w.deleteLater()
            except AttributeError:
                pass

        if self.current_graph_widget is None:
            if self.area.subWindowList():
                self.area.activateNextSubWindow()

    @QtCore.pyqtSlot(str)
    def set_graph(self, key: str):
        w = self.management.graphs[key]

        subwindow = self.area.addSubWindow(w)
        subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandMove, True)
        subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandResize, True)
        subwindow.setMinimumHeight(400)
        subwindow.setMinimumWidth(600)
        subwindow.windowStateChanged.connect(w.state_changed)


        self.datawidget.blockSignals(True)
        self.datawidget.tree.blockSignals(True)
        self.datawidget.add_graph(w.id, w.title)
        self.datawidget.tree.blockSignals(False)
        self.datawidget.blockSignals(False)

        w.mousePositionChanged.connect(self.mousemoved)
        w.aboutToClose.connect(self.delete_windows)
        w.positionClicked.connect(self.point_selected)
        w.show()

        graph_list = self.management.graphs.list()
        self.t1tauwidget.set_graphs(graph_list)
        self.ptsselectwidget.set_graphs(graph_list)

    @QtCore.pyqtSlot(QtWidgets.QMdiSubWindow, name='on_area_subWindowActivated')
    def change_window(self, wd: QtWidgets.QMdiSubWindow | None):
        """ Called every time focus moves from or to a subwindow. Returns None if current focus is not on a subwindow"""
        if wd is not None:
            if self.current_graph_widget is not None:
                self.current_graph_widget.closable = True
                self.current_graph_widget.graph_label.setText('')

                if self.ptsselectwidget.isVisible():
                    self._select_ptswidget(False, False, False)

                if self.fit_dialog.isVisible():
                    self._select_fitwidget(False, False)

            self.current_graph_widget = wd.widget()
            self.management.current_graph = wd.widget().id
            self.current_plotitem = self.current_graph_widget.graphic

            self.change_mouse_mode(self.actionMouse_behaviour.isChecked())

            if wd.isMaximized():
                self.current_graph_widget.graph_label.setText(self.current_graph_widget.title)
            else:
                self.current_graph_widget.graph_label.setText('')

            pick = False
            block = False
            if self.ptsselectwidget.isVisible():
                pick, block = self._select_ptswidget(True, pick, block)
            if self.fit_dialog.isVisible():
                block = self._select_fitwidget(True, block)

            self._set_pick_block(pick, block)

            self.datawidget.tree.blockSignals(True)
            self.datawidget.tree.highlight(self.management.current_graph)
            self.datawidget.tree.blockSignals(False)

    @QtCore.pyqtSlot(name='on_actionCascade_windows_triggered')
    @QtCore.pyqtSlot(name='on_actionTile_triggered')
    def change_window_size(self):
        if self.sender() == self.actionCascade_windows:
            self.area.cascadeSubWindows()
        elif self.sender() == self.actionTile:
            self.area.tileSubWindows()

    @QtCore.pyqtSlot(name='on_actionChange_datatypes_triggered')
    def type_change_dialog(self):
        from ..data.conversion import ConversionDialog

        dialog = ConversionDialog(self)
        dialog.set_graphs(self.management.graphs.tree())
        dialog.convertSets.connect(self.management.convert_sets)

        _ = dialog.exec()

        dialog.disconnect()

    def _set_pick_block(self, pick: bool, block: bool):
        self.current_graph_widget.enable_picking(pick)
        self.current_graph_widget.closable = not block

    def _show_tab(self, mode: str):
        widget, name = {
            't1_temp': (self.t1tauwidget, 'T1 mininmon'),
            'signal': (self.editsignalwidget, 'Signals'),
            'pick': (self.ptsselectwidget, 'Pick points'),
            'fit': (self.fit_dialog, 'Fit'),
            'drawing': (self.drawingswidget, 'Draw'),
            'integrate': (self.integralwidget, 'Integrate'),
        }[mode]

        for idx in range(self.tabWidget.count()):
            if self.tabWidget.widget(idx) == widget:
                self.tabWidget.setCurrentIndex(idx)
                return

        self.tabWidget.addTab(widget, name)
        self.tabWidget.setCurrentIndex(self.tabWidget.count()-1)

    @QtCore.pyqtSlot(int, name='on_tabWidget_tabCloseRequested')
    def close_tab(self, idx: int):
        if idx == 0:
            pass
        else:
            self.tabWidget.setCurrentIndex(0)
            self.tabWidget.removeTab(idx)

    @QtCore.pyqtSlot(int, name='on_tabWidget_currentChanged')
    def toggle_tabs(self, idx: int):
        widget = self.tabWidget.widget(idx)
        if self.current_graph_widget is None:
            if self.tabWidget.currentIndex() > 1:
                self.tabWidget.removeTab(self.tabWidget.indexOf(widget))
                self.tabWidget.setCurrentIndex(0)
            return

        if self.current_graph_widget is not None:
            self.current_graph_widget.enable_picking(False)

        pick_required, block_window = self._select_ptswidget(widget == self.ptsselectwidget, False, False)
        self._select_valuewidget(widget == self.valuewidget)
        pick_required, block_window = self._select_t1tauwidget(widget == self.t1tauwidget, pick_required, block_window)
        block_window = self._select_fitwidget(widget == self.fit_dialog, block_window)
        self._select_drawingswidget(widget == self.drawingswidget)
        pick_required = self._select_integralwidget(widget == self.integralwidget, pick_required, block_window)

        self._set_pick_block(pick_required, block_window)

    def _select_ptswidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
        if self.current_graph_widget is None:
            return pick_required, block_window

        if onoff:  # point selection
            for line in self.ptsselectwidget.pts_lines:
                self.current_graph_widget.add_external(line)
            self.ptsselectwidget.point_removed.connect(self.current_graph_widget.remove_external)
            self.ptsselectwidget.connected_figure = self.management.current_graph
            pick_required = True
        else:
            if self.ptsselectwidget.connected_figure:
                g = self.management.graphs[self.ptsselectwidget.connected_figure]
                for line in self.ptsselectwidget.pts_lines:
                    g.remove_external(line)
            # self.ptsselectwidget.clear()

        return pick_required, block_window

    def _select_valuewidget(self, onoff: bool):
        if onoff:  # Values
            self.valuewidget(self.management.graphs.tree())
            current_graph = self.valuewidget.connected_figure
            if current_graph is not None:
                self.management.graphs[current_graph].add_external(self.valuewidget.selection_real)
                self.management.graphs[current_graph].add_external(self.valuewidget.selection_imag)
        else:
            if self.valuewidget.connected_figure is not None:
                self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_real)
                self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_imag)

    def _select_integralwidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
        if self.current_graph_widget is None:
            return pick_required, block_window

        if onoff:
            self.integralwidget(self.current_graph_widget.title,
                                self.management.graphs.current_sets(self.management.current_graph))
            self.integralwidget.item_deleted.connect(self.current_graph_widget.remove_external)
            self.integralwidget.connected_figure = self.management.current_graph
            pick_required = True
            block_window = True
        else:
            if self.integralwidget.connected_figure:
                g = self.management.graphs[self.integralwidget.connected_figure]
                for line in self.integralwidget.lines:
                    g.remove_external(line[0])
                    g.remove_external(line[1])
            self.integralwidget.clear()

        return pick_required, block_window

    def _select_t1tauwidget(self, onoff: bool, pick_required: bool, block_window: bool):
        if onoff:  # tau from t1
            if self.current_graph_widget is None:
                return pick_required, block_window

            idx = self.tabWidget.indexOf(self.t1tauwidget)
            if len(self.current_graph_widget) != 1:
                QtWidgets.QMessageBox.information(self, 'Too many datasets',
                                                  'Only one T1 curve can be evaluated at once')
                self.tabWidget.removeTab(idx)
                self.tabWidget.setCurrentIndex(0)
                return pick_required, block_window

            self.tabWidget.setTabText(idx, 'T1 min (%s)' % {self.current_graph_widget.title})

            self.t1tauwidget.set_graphs(self.management.graphs.list())
            self.t1tauwidget.set_data(*self.management.get_data(self.current_graph_widget.active[0], xy_only=True),
                                      name=self.management[self.current_graph_widget.active[0]].name)
            self.t1tauwidget.connected_figure = self.management.current_graph
            self.current_graph_widget.add_external(self.t1tauwidget.min_pos)
            self.current_graph_widget.add_external(self.t1tauwidget.parabola)
            pick_required = True
            block_window = True
        else:
            if self.t1tauwidget.connected_figure:
                g = self.management.graphs[self.t1tauwidget.connected_figure]
                g.remove_external(self.t1tauwidget.min_pos)
                g.remove_external(self.t1tauwidget.parabola)

        return pick_required, block_window

    def _select_drawingswidget(self, onoff):
        if onoff:
            if self.drawingswidget.graphs is None:
                self.drawingswidget.graphs = self.management.graphs
            self.drawingswidget.update_tree()
        else:
            self.drawingswidget.clear()

    @QtCore.pyqtSlot(str)
    def get_data(self, key: str):
        self.sender().set_data(self.management[key])

    @QtCore.pyqtSlot(name='on_actionCalculateT1_triggered')
    def show_t1calc_dialog(self):
        dialog = QRelaxCalc(self)
        dialog.set_graphs(self.management.graphs.tree(key_only=True))
        dialog.newData.connect(self.management.calc_relaxation)
        dialog.show()

    @QtCore.pyqtSlot(name='on_actionSkip_points_triggered')
    def skip_pts_dialog(self):
        from ..math.skipping import QSkipDialog

        dial = QSkipDialog(self)
        dial.exec()

        self.management.skip_points(**dial.get_arguments())

    @QtCore.pyqtSlot(name='on_action_coup_calc_triggered')
    def coupling_dialog(self):
        dialog = QCoupCalcDialog(self)
        dialog.show()

    @QtCore.pyqtSlot(name='on_action_mean_t1_triggered')
    def mean_dialog(self):
        gnames = self.management.graphs.tree(key_only=True)
        dialog = QMeanTimes(gnames, parent=self)
        dialog.newValues.connect(self.management.calc_mean)
        dialog.show()

    @QtCore.pyqtSlot(name='on_actionRunning_values_triggered')
    def smooth_dialog(self):
        dialog = QSmooth(parent=self)
        dialog.newValues.connect(self.management.smooth_data)
        dialog.show()

    @QtCore.pyqtSlot(name='on_actionInterpolation_triggered')
    def interpol_dialog(self):
        if self.current_graph_widget is None:
            return

        gnames = self.management.graphs.tree()
        dialog = InterpolDialog(parent=self)
        dialog.set_data(gnames, self.current_graph_widget.id)
        dialog.new_data.connect(self.management.interpolate_data)
        dialog.show()

    @QtCore.pyqtSlot(name='on_action_calc_triggered')
    def open_eval_dialog(self):
        if self.eval is None:
            self.eval = QEvalDialog(parent=self)
            self.eval.do_eval.connect(self.management.eval_expression)
            self.eval.do_calc.connect(self.management.create_from_function)

        self.eval.set_mode('e')
        self.eval.set_namespace(self.management.get_namespace())
        self.eval.add_data(self.management.active_sets)
        self.eval.set_graphs(self.management.graphs.list())

        self.eval.exec()

    @QtCore.pyqtSlot(name='on_actionAddlines_triggered')
    def make_data_from_function(self):
        if self.eval is None:
            self.eval = QEvalDialog(parent=self)
            self.eval.do_eval.connect(self.management.eval_expression)
            self.eval.do_calc.connect(self.management.create_from_function)

        self.eval.set_mode('c')
        self.eval.set_namespace(self.management.get_namespace())
        self.eval.set_graphs(self.management.graphs.list())

        self.eval.exec()

    @QtCore.pyqtSlot(name='on_actionDerivation_triggered')
    # @QtCore.pyqtSlot(name='on_actionIntegration_triggered')
    @QtCore.pyqtSlot(name='on_actionFilon_triggered')
    def int_diff_ft(self):
        sets = self.management.active_sets
        mode = {
            # self.actionIntegration: 'int',
            self.actionDerivation: 'diff',
            self.actionFilon: 'logft'}[self.sender()]

        if sets:
            from ..math.integrate_derive import QDeriveIntegrate

            dialog = QDeriveIntegrate(mode)
            dialog.add_graphs(self.management.graphs.list())
            dialog.set_sets(sets)

            res = dialog.exec()

            if res:
                options = dialog.get_options()
                mode = options['mode']
                if mode in ['i', 'd']:
                    self.management.integrate(**options)
                elif mode == 'l':
                    self.management.logft(**options)
                else:
                    raise ValueError('Unknown mode %s, not `i`, `d`, `l`.' % str(mode))

    @QtCore.pyqtSlot(str)
    def update_data(self, sid: str):
        if self.valuewidget.shown_set == sid:
            self.show_data_values(sid)

        self.datawidget.set_name(sid, self.management[sid].name)

    @QtCore.pyqtSlot(str)
    def delete_data(self, sid):
        if self.valuewidget.shown_set == sid:
            self.tabWidget.setCurrentIndex(0)

        self.datawidget.remove_item(sid)

    @QtCore.pyqtSlot(name='on_actionBaseline_triggered')
    def baseline_dialog(self):
        if not self.current_graph_widget:
            return

        if len(self.current_graph_widget) != 1:
            QtWidgets.QMessageBox.information(self, 'Invalid number of sets',
                                              'Baseline correction can only applied to one set at a time.')
            return

        for sid in self.current_graph_widget.active:
            data_mode = self.management[sid].mode
            if data_mode in ['spectrum']:
                editor = QBaselineDialog(self)
                editor.add_data(*self.management.get_data(sid, xy_only=True))
                editor.finished.connect(self.management.apply)
                editor.exec()

    @QtCore.pyqtSlot(str)
    def do_preview(self, mode):

        if mode == 'ap':
            dialog = QApodDialog(parent=self)
        elif mode == 'ph':
            dialog = QPhasedialog(parent=self)
        else:
            raise ValueError('Unknown preview mode %s' % str(mode))

        dialog.setRange(*self.current_graph_widget.ranges, self.current_graph_widget.log)

        for sid in self.current_graph_widget.active:
            data_mode = self.management[sid].mode
            tobeadded = False
            if (data_mode == 'fid') or (data_mode == 'spectrum' and mode == 'ph'):
                tobeadded = True

            if tobeadded:
                dialog.add_data(*self.management.get_data(sid, xy_only=True))

        if dialog.exec() == QtWidgets.QDialog.Accepted:
            self.management.apply(mode, dialog.get_value())

    @QtCore.pyqtSlot(name='on_actionMove_between_plots_triggered')
    def move_sets_dialog(self):
        gnames = self.management.graphs.tree()
        self.movedialog.setup(gnames)
        self.movedialog.exec()

    @QtCore.pyqtSlot(list, str, str)
    def move_sets(self, sets, dest, src):
        self.management.move_sets(sets, dest, src)
        for s in sets:
            self.datawidget.tree.move_sets(s, dest, src)

    @QtCore.pyqtSlot(str)
    def show_data_values(self, sid: str):
        if sid == '':
            return

        data, mask = self.management.get_data(sid)
        self.valuewidget.set_data(data, mask)

    def plot_selected_values(self, old_gid: str, new_gid: str):
        for pts in (self.valuewidget.selection_real, self.valuewidget.selection_imag):
            if old_gid:
                self.management.graphs[old_gid].remove_external(pts)
            if new_gid:
                self.management.graphs[new_gid].add_external(pts)

    @QtCore.pyqtSlot(object, str)
    def item_to_graph(self, item, graph_id):
        self.management.graphs[graph_id].add_external(item)

    @QtCore.pyqtSlot(object, str)
    def item_from_graph(self, item, graph_id):
        self.management.graphs[graph_id].remove_external(item)

    def closeEvent(self, evt):
        # self._write_settings()
        self.close()

    @QtCore.pyqtSlot(int)
    def request_data(self, idx):
        idd = self.datawidget.get_indexes(idx=idx-1)
        try:
            x = self.management[idd].x
            y = self.management[idd].y
            ret_val = (x, y)
        except KeyError:
            ret_val = None

        self.sender().receive_data(ret_val)

        return ret_val

    @QtCore.pyqtSlot(tuple, bool)
    def point_selected(self, pos, double):
        w = self.tabWidget.currentWidget()
        if w == self.ptsselectwidget:
            line = self.ptsselectwidget.add(pos, double)
            self.current_graph_widget.add_external(line)

        elif w == self.t1tauwidget:
            self.t1tauwidget.t1min_picked(pos)

        elif w == self.integralwidget:
            region, integral_plot  = self.integralwidget.add(pos)
            self.current_graph_widget.add_external(region)
            self.current_graph_widget.add_external(integral_plot)

    def _select_fitwidget(self, onoff: bool, block_window: bool):
        if self.current_graph_widget is not None:
            pass

        if onoff:
            self.fit_dialog.connected_figure = self.management.current_graph
            self.fit_dialog.load(self.management.active_sets)
            for item in self.fit_dialog.preview_lines:
                self.current_graph_widget.add_external(item)
            if self.action_custom_range.isChecked():
                self.current_graph_widget.add_external(self.fitregion)

            block_window = True
        else:
            for item in self.fit_dialog.preview_lines:
                self.current_graph_widget.remove_external(item)
            self.current_graph_widget.remove_external(self.fitregion)

        return block_window

    @QtCore.pyqtSlot(QtWidgets.QAction)
    def change_fit_limits(self, action: QtWidgets.QAction):
        if action == self.action_custom_range and self.fit_dialog.isVisible():
            self.current_graph_widget.add_external(self.fitregion)
        else:
            self.current_graph_widget.remove_external(self.fitregion)

    def start_fit(self, parameter, links, fit_options):
        fit_options['limits'] = {
            self.action_no_range: 'none',
            self.action_x_range: 'x',
            self.action_custom_range: self.fitregion.getRegion()
        }[self.ac_group2.checkedAction()]

        fit_options['fit_mode'] = {
            self.action_lm_fit: 'lsq',
            self.action_nm_fit: 'nm',
            self.action_odr_fit: 'odr'
        }[self.ac_group.checkedAction()]

        self.fit_dialog.fit_button.setEnabled(False)
        self.management.start_fit(parameter, links, fit_options)

    @QtCore.pyqtSlot(dict, int, bool)
    def show_fit_preview(self, funcs: dict, num: int, show: bool):
        if self.fit_dialog.connected_figure is None:
            return

        g = self.management.graphs[self.fit_dialog.connected_figure]
        for item in self.fit_dialog.preview_lines:
            g.remove_external(item)

        if show:
            x_lim, _ = g.ranges
            space = geomspace if g.log[0] else linspace
            x = space(1.01*x_lim[0], 0.99*x_lim[1], num=num)

            self.fit_dialog.make_previews(x, funcs)

            for item in self.fit_dialog.preview_lines:
                g.add_external(item)

    @QtCore.pyqtSlot(list)
    def show_fit_results(self, results: list):
        self.fit_dialog.fit_button.setEnabled(True)
        if results:
            res_dialog = QFitResult(results, self.management, parent=self)
            res_dialog.add_graphs(self.management.graphs.list())
            res_dialog.closed.connect(self.accepts_fit)
            res_dialog.redoFit.connect(self.management.redo_fits)
            res_dialog.show()

    @QtCore.pyqtSlot(dict, list, str, bool, dict)
    def accepts_fit(self, res: dict, opts: list, param_graph: str, show_fit: bool, parts: dict) -> None:
        self.fit_dialog.set_parameter(res)
        self.management.make_fits(res, opts, param_graph, show_fit, parts)

    @QtCore.pyqtSlot(name='on_actionFunction_editor_triggered')
    def edit_models(self):
        if self.editor is None:
            from ..lib.usermodeleditor import QUsermodelEditor

            self.editor = QUsermodelEditor(config_paths() / 'usermodels.py', parent=self)
            self.editor.modelsChanged.connect(lambda: self.fit_dialog.read_and_load_functions())
            self.editor.setWindowModality(QtCore.Qt.ApplicationModal)
        self.editor.show()

    @QtCore.pyqtSlot(name='on_action_create_fit_function_triggered')
    def open_fitmodel_wizard(self):
        from ..fit.function_creation_dialog import QUserFitCreator

        helper = QUserFitCreator(config_paths() / 'usermodels.py', parent=self)
        helper.classCreated.connect(lambda: self.fit_dialog.read_and_load_functions())

        helper.show()


    @QtCore.pyqtSlot(name='on_actionShift_triggered')
    def shift_dialog(self):
        s = QShift(self)
        s.set_graphs(self.management.graphs.list())
        for key, name in self.management.active_sets:
            data = self.management.data[key]
            s.add_item(key, name, data.x, data.y)
        s.valuesChanged.connect(self.management.shift_scale)
        s.show()

    @staticmethod
    @QtCore.pyqtSlot(name='on_actionDocumentation_triggered')
    def open_doc():
        docpath = '/autohome/dominik/auswerteprogramm3/doc/_build/html/index.html'
        import webbrowser
        webbrowser.open(docpath)

    def dropEvent(self, evt):
        if evt.mimeData().hasUrls():
            files = [str(url.toLocalFile()) for url in evt.mimeData().urls()]
            self.management.load_files(files)

    def dragEnterEvent(self, evt):
        evt.accept()

    @QtCore.pyqtSlot(bool, name='on_actionMouse_behaviour_toggled')
    def change_mouse_mode(self, is_checked: bool):
        if is_checked:
            self.current_plotitem.plotItem.vb.setMouseMode(ViewBox.RectMode)
        else:
            self.current_plotitem.plotItem.vb.setMouseMode(ViewBox.PanMode)

    def mousemoved(self, xpos, ypos):
        self.mousepos.setText(f'x={xpos:.3g}; y={ypos:.3g}')

    @QtCore.pyqtSlot(name='on_actionSnake_triggered')
    @QtCore.pyqtSlot(name='on_actionTetris_triggered')
    @QtCore.pyqtSlot(name='on_actionLife_triggered')
    @QtCore.pyqtSlot(name='on_actionMine_triggered')
    def spannung_spiel_und_spass(self):

        if self.sender() == self.actionLife:
            from ..lib.gol import QGameOfLife
            game = QGameOfLife(parent=self)
            game.setWindowModality(QtCore.Qt.NonModal)
            game.show()

        elif self.sender() == self.actionMine:
            from ..lib.stuff import QMines
            game = QMines(parent=self)
            game.setWindowModality(QtCore.Qt.NonModal)
            game.show()

        else:
            from ..lib.stuff import Game
            if self.sender() == self.actionSnake:
                gtype = 'snake'
            else:
                gtype = 'tetris'

            game = Game(gtype, parent=self)
            game.show()

    @QtCore.pyqtSlot(name='on_actionConfiguration_triggered')
    def open_configuration(self):
        from ..lib.configurations import GeneralConfiguration
        dialog = GeneralConfiguration(self)
        dialog.show()

    @QtCore.pyqtSlot(name='on_action_colorcycle_triggered')
    def open_color_dialog(self):
        from ..lib.color_dialog import ColorDialog
        dialog = ColorDialog(self)
        dialog.show()

    def close(self):
        write_state({'History': {'recent path': str(self.path)}})

        super().close()

    def read_state(self):
        opts = read_state()
        self.path = pathlib.Path(opts['History'].get('recent path', Path.home()))

    @QtCore.pyqtSlot(name='on_actionBugs_triggered')
    def report_bug(self):
        import webbrowser

        webbrowser.open('https://gitea.pkm.physik.tu-darmstadt.de/IPKM-Public/nmreval/issues/new')

    @QtCore.pyqtSlot(name='on_actionUpdate_triggered')
    def look_for_update(self):
        w = UpdateDialog(parent=self)
        w.show()

    @QtCore.pyqtSlot(name='on_actionShow_error_log_triggered')
    def open_log(self):
        from ..lib.logger import QLog

        QLog(parent=self).show()

