from __future__ import annotations

import copy

from numpy import argsort

from . import Relations
from ..Qt import QtWidgets, QtCore
from ..data.container import FitContainer
from ..graphs.graphwindow import QGraphWindow


class ApodizationCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, apod_values: list, apod_func: object):
        super().__init__('Apodization')

        self.__data = data
        self.__y = copy.deepcopy(data.data.y)
        self.__apod_func = apod_func
        self.__apod_values = apod_values

    def undo(self):
        # doing a copy (again) to ensure two different objects
        self.__data.y = copy.deepcopy(self.__y)

    def redo(self):
        self.__data.apply('ap', (self.__apod_values, self.__apod_func))


class CutCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, *limits):
        super().__init__('Apodization')

        self.__data = data
        self.__data_data = copy.deepcopy(data.data)
        self.__limits = limits

    def undo(self):
        # doing a copy (again) to ensure two different objects
        self.__data.data = copy.deepcopy(self.__data_data)

    def redo(self):
        self.__data.apply('cut', self.__limits)


class PhaseCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, ph0: float, ph1: float, pvt: float):
        super().__init__('Phase correction')

        self.__phase = (ph0, ph1, pvt)
        self.__data = data

    def undo(self):
        self.__data.apply('ph', (-self.__phase[0], -self.__phase[1], self.__phase[2]))

    def redo(self):
        self.__data.apply('ph', self.__phase)


class AutophaseCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, pvt: float):
        super().__init__('Autophase')

        self.__data = data
        self.__data_data = copy.deepcopy(data.data)
        self.__pvt = pvt

    def undo(self):
        self.__data.data = copy.deepcopy(self.__data_data)

    def redo(self):
        self.__data.apply('autoph', (self.__pvt,))


class ShiftCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, value, mode):
        super().__init__('Fourier')

        self.__data = data
        self.__original = copy.deepcopy(self.__data.data)
        self.__args = (value, mode)

    def undo(self):
        self.__data.data = copy.deepcopy(self.__original)

    def redo(self):
        self.__data.apply('ls', self.__args)


class EditCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, *args):
        super().__init__('Edit signal')

        self.__data = data
        self.__arguments = args
        self.__original = copy.deepcopy(self.__data.data)

    def undo(self):
        self.__data.data = copy.deepcopy(self.__original)

    def redo(self):
        self.__data.edit_signal(*self.__arguments)


class NormCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, mode):
        super().__init__('Normalize')

        self.__data = data
        self.__mode = mode
        self.__scale = 1.

    def undo(self):
        self.__data.y *= self.__scale
        self.__data.y_err *= self.__scale

    def redo(self):
        max_value = self.__data.y.max()
        self.__data.apply('norm', (self.__mode,))
        self.__scale = max_value / self.__data.y.max()


class CenterCommand(QtWidgets.QUndoCommand):
    def __init__(self, data):
        super().__init__('Normalize')

        self.__data = data
        self.__offset = 0.

    def undo(self):
        _x = self.__data.data.x
        _x += self.__offset
        self.__data.x = _x

    def redo(self):
        x0 = self.__data.x[0]
        self.__data.apply('center', ())
        self.__offset = x0 - self.__data.x[0]


class ZerofillCommand(QtWidgets.QUndoCommand):
    def __init__(self, data):
        super().__init__('Zero filling')

        self.__data = data

    def undo(self):
        self.__data.apply('zf', (-1,))

    def redo(self):
        self.__data.apply('zf', (1,))


class BaselineCommand(QtWidgets.QUndoCommand):
    def __init__(self, data):
        super().__init__('Baseline correction')

        self.__baseline = None
        self.__data = data

    def undo(self):
        self.__data.y += self.__baseline

    def redo(self):
        y_prev = self.__data.y[-1]
        self.__data.apply('bl', tuple())
        self.__baseline = y_prev - self.__data.y[-1]


class BaselineSplineCommand(QtWidgets.QUndoCommand):
    def __init__(self, data, baseline):
        super().__init__('Baseline correction')

        self.__baseline = baseline
        self.__data = data

    def undo(self):
        self.__data.apply('bls', (-self.__baseline,))

    def redo(self):
        self.__data.apply('bls', (self.__baseline,))


class FourierCommand(QtWidgets.QUndoCommand):
    def __init__(self, data):
        super().__init__('Fourier')

        self.__data = data
        self.__original = copy.deepcopy(self.__data.data)

    def undo(self):
        self.__data.data = copy.deepcopy(self.__original)

    def redo(self):
        self.__data.apply('ft', tuple())


class SortCommand(QtWidgets.QUndoCommand):
    def __init__(self, data):
        super().__init__('Sort')

        self.__data = data
        self.__sort = None

    def undo(self):
        self.__data.unsort(self.__sort)

    def redo(self):
        self.__sort = argsort(argsort(self.__data.data.x))
        self.__data.apply('sort', tuple())


class DeleteGraphCommand(QtWidgets.QUndoCommand):
    def __init__(self, container: dict, key: str,
                 signal1: QtCore.pyqtSignal, signal2: QtCore.pyqtSignal):
        super().__init__('Delete graph')
        # Deletion of GraphWindow is more complicated because C++ object is destroyed

        self.__container = container
        _value = self.__container[key]
        self.__value = self.__container[key].get_state()
        self.__key = key
        self.__signal_add = signal1
        self.__signal_remove = signal2

    def redo(self):
        self.__signal_remove.emit(self.__key)
        del self.__container[self.__key]

    def undo(self):
        q = QGraphWindow().set_state(self.__value)
        self.__container[self.__key] = q
        self.__signal_add.emit(self.__key)


class DeleteCommand(QtWidgets.QUndoCommand):
    def __init__(self, container: dict, keys: list[str], graphs: dict, graphid: str,
                 signal1: QtCore.pyqtSignal, signal2: QtCore.pyqtSignal):
        super().__init__('Delete data')

        self.__container = container
        self.__graph_container = graphs
        self.__graph_key = graphid
        self.__value = {}
        for k in keys:
            self.__value[k] = self.__container[k]
        self.__keys = tuple(keys)
        self.__signal_add = signal1
        self.__signal_remove = signal2

    def redo(self):
        # stop graph from rescaling and updating legend
        self.__graph_container[self.__graph_key].block(True)

        self.__signal_remove.emit(list(self.__keys[::-1]))
        for sid in self.__keys[::-1]:
            val = self.__value[sid]

            if isinstance(val, FitContainer):
                try:
                    self.__container[val.fitted_key]._fits.remove(sid)
                except KeyError:
                    pass

            for (flag1, flag2) in ((Relations.isFitPartOf, Relations.hasFitPart), (Relations.hasFitPart, Relations.isFitPartOf)):
                for related_item in val.get_relation(flag1):
                    try:
                        self.__container[related_item].remove_relation(flag2, sid)
                    except KeyError:
                        pass

            del self.__container[sid]

        self.__graph_container[self.__graph_key].block(False)

    def undo(self):
        # stop graph from rescaling and updating legend
        self.__graph_container[self.__graph_key].block(True)

        for sid in self.__keys:
            val = self.__value[sid]
            self.__container[sid] = val

            if isinstance(val, FitContainer):
                try:
                    self.__container[val.fitted_key]._fits.append(sid)
                except KeyError:
                    pass

            for (flag1, flag2) in (('isFitPartOf', 'hasFitPartOf'), ('hasFitPartOf', 'isFitPartOf')):
                for related_item in val.get_relation(flag1):
                    try:
                        self.__container[related_item].add_relation(flag2, sid)
                    except KeyError:
                        pass

        self.__signal_add.emit(list(self.__keys), self.__graph_key)

        self.__graph_container[self.__graph_key].block(False)


class EvalCommand(QtWidgets.QUndoCommand):
    def __init__(self, container: dict, key: str, new_data, title: str):
        super().__init__(title)
        self.__container = container
        self.__value = copy.deepcopy(self.__container[key].data)
        self.__replacement = new_data
        self.__key = key

    def redo(self):
        self.__container[self.__key].data = self.__replacement

    def undo(self):
        self.__container[self.__key].data = self.__value
