from __future__ import annotations

import pathlib
import re
from pathlib import Path

from numpy import geomspace, linspace
from pyqtgraph import ViewBox, PlotDataItem

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 OpenFileDialog, SaveDirectoryDialog
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


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()

    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._values_plot = PlotDataItem(x=[], y=[], symbolSize=30, symbol='x',
                                         pen=None, symbolPen='#d526b5', symbolBrush='#d526b5')

        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.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.actionIntegrate.triggered.connect(lambda: self._show_tab('integrate'))
        self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit'))

        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',
                                    filters='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)

                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):
        fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save fit parameter', directory=str(self.path),
                                                         filter='All files(*, *);;Text files(*.dat *.txt)')

        if fname:
            self.management.save_fit_parameter(fname, 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:
                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)

        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):
        """ 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

                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())

            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

    @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._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

    @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)

    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')
        }[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)

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

    def _select_integralwidget(self, onoff: bool, pick_required: bool):
        if self.current_graph_widget is None:
            return pick_required

        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
        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

    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

    @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, gid: str, x: list, y: list):
        self._values_plot.setData(x=x, y=y)
        if gid != self.valuewidget.connected_figure and self.valuewidget.connected_figure is not None:
            self.management.graphs[self.valuewidget.connected_figure].remove_external(self._values_plot)
            self.management.graphs[gid].add_external(self._values_plot)
            self.valuewidget.connected_figure = gid

    @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)

    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(self.update_fitmodels)
            self.editor.setWindowModality(QtCore.Qt.ApplicationModal)
        self.editor.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()

    def update_fitmodels(self):
        pass

    @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({'recent_path': str(self.path)})

        super().close()

    def read_state(self):
        opts = read_state()
        self.path = pathlib.Path(opts.get('recent_path', Path.home()))