from PyQt6.QtCore import QPoint, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import QWidget, QSizePolicy, QCheckBox, QMessageBox
from PyQt6.QtGui import QUndoStack

from urh import settings
from urh.controller.widgets.SignalFrame import SignalFrame
from urh.signalprocessing.Signal import Signal
from urh.ui.ui_tab_interpretation import Ui_Interpretation
from urh.util import util


class SignalTabController(QWidget):
    frame_closed = pyqtSignal(SignalFrame)
    not_show_again_changed = pyqtSignal()
    signal_created = pyqtSignal(int, Signal)
    files_dropped = pyqtSignal(list)
    frame_was_dropped = pyqtSignal(int, int)

    @property
    def num_frames(self):
        return len(self.signal_frames)

    @property
    def signal_frames(self):
        """

        :rtype: list of SignalFrame
        """
        splitter = self.ui.splitter
        return [
            splitter.widget(i)
            for i in range(splitter.count())
            if isinstance(splitter.widget(i), SignalFrame)
        ]

    @property
    def signal_undo_stack(self):
        return self.undo_stack

    def __init__(self, project_manager, parent=None):
        super().__init__(parent)
        self.ui = Ui_Interpretation()
        self.ui.setupUi(self)

        util.set_splitter_stylesheet(self.ui.splitter)

        self.ui.placeholderLabel.setVisible(False)
        self.getting_started_status = None
        self.__set_getting_started_status(True)

        self.undo_stack = QUndoStack()
        self.project_manager = project_manager

        self.drag_pos = None

    def on_files_dropped(self, files):
        self.files_dropped.emit(files)

    def close_frame(self, frame: SignalFrame):
        self.frame_closed.emit(frame)

    def add_signal_frame(self, proto_analyzer, index=-1):
        self.__set_getting_started_status(False)
        sig_frame = SignalFrame(
            proto_analyzer, self.undo_stack, self.project_manager, parent=self
        )
        sframes = self.signal_frames

        if len(proto_analyzer.signal.filename) == 0:
            # new signal from "create signal from selection"
            sig_frame.ui.btnSaveSignal.show()

        self.__create_connects_for_signal_frame(signal_frame=sig_frame)
        sig_frame.signal_created.connect(self.emit_signal_created)
        sig_frame.not_show_again_changed.connect(self.not_show_again_changed.emit)
        sig_frame.ui.lineEditSignalName.setToolTip(
            self.tr("Sourcefile: ") + proto_analyzer.signal.filename
        )
        sig_frame.apply_to_all_clicked.connect(self.on_apply_to_all_clicked)

        prev_signal_frame = sframes[-1] if len(sframes) > 0 else None
        if prev_signal_frame is not None and hasattr(prev_signal_frame, "ui"):
            sig_frame.ui.cbProtoView.setCurrentIndex(
                prev_signal_frame.ui.cbProtoView.currentIndex()
            )

        sig_frame.blockSignals(True)

        index = self.num_frames if index == -1 else index
        sig_frame.setSizePolicy(
            QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding
        )
        self.ui.splitter.insertWidget(index, sig_frame)
        sig_frame.blockSignals(False)

        default_view = settings.read("default_view", 0, int)
        sig_frame.ui.cbProtoView.setCurrentIndex(default_view)

        return sig_frame

    def add_empty_frame(self, filename: str, proto):
        self.__set_getting_started_status(False)
        sig_frame = SignalFrame(
            proto_analyzer=proto,
            undo_stack=self.undo_stack,
            project_manager=self.project_manager,
            parent=self,
        )

        sig_frame.ui.lineEditSignalName.setText(filename)
        self.__create_connects_for_signal_frame(signal_frame=sig_frame)

        self.ui.splitter.insertWidget(self.num_frames, sig_frame)

        return sig_frame

    def __set_getting_started_status(self, getting_started: bool):
        if getting_started == self.getting_started_status:
            return

        self.getting_started_status = getting_started
        self.ui.labelGettingStarted.setVisible(getting_started)

        if not getting_started:
            w = QWidget()
            w.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
            self.ui.splitter.addWidget(w)

    def __create_connects_for_signal_frame(self, signal_frame: SignalFrame):
        signal_frame.hold_shift = settings.read("hold_shift_to_drag", True, type=bool)
        signal_frame.drag_started.connect(self.frame_dragged)
        signal_frame.frame_dropped.connect(self.frame_dropped)
        signal_frame.files_dropped.connect(self.on_files_dropped)
        signal_frame.closed.connect(self.close_frame)

    def set_frame_numbers(self):
        for i, f in enumerate(self.signal_frames):
            f.ui.lSignalNr.setText("{0:d}:".format(i + 1))

    @pyqtSlot()
    def save_all(self):
        if self.num_frames == 0:
            return

        try:
            not_show = settings.read("not_show_save_dialog", False, type=bool)
        except TypeError:
            not_show = False

        if not not_show:
            cb = QCheckBox("Don't ask me again.")
            msg_box = QMessageBox(
                QMessageBox.Icon.Question,
                self.tr("Confirm saving all signals"),
                self.tr("All changed signal files will be overwritten. OK?"),
            )
            msg_box.addButton(QMessageBox.StandardButton.Yes)
            msg_box.addButton(QMessageBox.StandardButton.No)
            msg_box.setCheckBox(cb)

            reply = msg_box.exec()
            not_show_again = cb.isChecked()
            settings.write("not_show_save_dialog", not_show_again)
            self.not_show_again_changed.emit()

            if reply != QMessageBox.StandardButton.Yes:
                return

        for f in self.signal_frames:
            if f.signal is None or f.signal.filename == "":
                continue
            f.signal.save()

    @pyqtSlot()
    def close_all(self):
        for f in self.signal_frames:
            f.my_close()

    @pyqtSlot(Signal)
    def on_apply_to_all_clicked(self, signal: Signal):
        for frame in self.signal_frames:
            if frame.signal is not None:
                frame.signal.noise_min_plot = signal.noise_min_plot
                frame.signal.noise_max_plot = signal.noise_max_plot

                frame.signal.block_protocol_update = True
                proto_needs_update = False

                if frame.signal.modulation_type != signal.modulation_type:
                    frame.signal.modulation_type = signal.modulation_type
                    proto_needs_update = True

                if frame.signal.center != signal.center:
                    frame.signal.center = signal.center
                    proto_needs_update = True

                if frame.signal.tolerance != signal.tolerance:
                    frame.signal.tolerance = signal.tolerance
                    proto_needs_update = True

                if frame.signal.noise_threshold != signal.noise_threshold:
                    frame.signal.noise_threshold_relative = (
                        signal.noise_threshold_relative
                    )
                    proto_needs_update = True

                if frame.signal.samples_per_symbol != signal.samples_per_symbol:
                    frame.signal.samples_per_symbol = signal.samples_per_symbol
                    proto_needs_update = True

                if frame.signal.pause_threshold != signal.pause_threshold:
                    frame.signal.pause_threshold = signal.pause_threshold
                    proto_needs_update = True

                if frame.signal.message_length_divisor != signal.message_length_divisor:
                    frame.signal.message_length_divisor = signal.message_length_divisor
                    proto_needs_update = True

                frame.signal.block_protocol_update = False

                if proto_needs_update:
                    frame.signal.protocol_needs_update.emit()

    @pyqtSlot(QPoint)
    def frame_dragged(self, pos: QPoint):
        self.drag_pos = pos

    @pyqtSlot(QPoint)
    def frame_dropped(self, pos: QPoint):
        start = self.drag_pos
        if start is None:
            return

        end = pos
        start_index = -1
        end_index = -1
        if self.num_frames > 1:
            for i, w in enumerate(self.signal_frames):
                if w.geometry().contains(start):
                    start_index = i

                if w.geometry().contains(end):
                    end_index = i

        self.swap_frames(start_index, end_index)
        self.frame_was_dropped.emit(start_index, end_index)

    @pyqtSlot(int, int)
    def swap_frames(self, from_index: int, to_index: int):
        if from_index != to_index:
            start_sig_widget = self.ui.splitter.widget(from_index)
            self.ui.splitter.insertWidget(to_index, start_sig_widget)

    @pyqtSlot()
    def on_participant_changed(self):
        for sframe in self.signal_frames:
            sframe.on_participant_changed()

    def redraw_spectrograms(self):
        for frame in self.signal_frames:
            if frame.ui.gvSpectrogram.width_spectrogram > 0:
                frame.draw_spectrogram(force_redraw=True)

    @pyqtSlot(Signal)
    def emit_signal_created(self, signal):
        try:
            index = self.signal_frames.index(self.sender()) + 1
        except ValueError:
            index = -1

        self.signal_created.emit(index, signal)
