"""Worker that connects to a Processor object."""

from pathlib import Path
from typing import Any

from PyQt6.QtCore import QObject
from PyQt6.QtCore import pyqtSignal as Signal
from PyQt6.QtCore import pyqtSlot as Slot

from pymagnetos.core import BaseProcessor


class BaseWorker(QObject):
    """
    Wrapper around a Processor object.

    It exposes the Processor methods as slots and emits a signal when the task is
    finished. This allows putting the worker in a separate thread to keep the app
    responsive.

    Subclasses must register the type of Processor before calling the Base `__init__()`
    method. It is initialized with any arguments the Processor class accepts.

    Signals
    -------
    sig_load_data_finished : emits when data is loaded.
    sig_compute_field_finished : emits when the magnetic field is computed.
    sig_preprocess_finished : emits when preprocessing steps are finished.
    sig_align_finished : emits when the magnetic field is aligned on the data.
    sig_analysis_finished : emits when the analysis is finished.
    sig_save_nexus_finished : emits when the NeXus file is saved.
    sig_batch_finished : should be used to emit when the batch processing is finished.
    sig_preprocess_progress : should be used to emit step (int) during preprocessing.
    sig_batch_process_progress : should be used to emit step (int) index during
        batch-processing.
    """

    _type_processor: type[BaseProcessor] = BaseProcessor

    # Additional actions to take after loading
    _after_load_preprocess: bool = False
    _after_load_align: bool = False
    _after_load_analysis: bool = False

    # Additional arguments for the load, preprocess, run_analysis methods
    _kwargs_load: dict[str, Any] = {}
    _kwargs_preprocess: dict[str, Any] = {}
    _kwargs_analysis: dict[str, Any] = {}

    proc: BaseProcessor
    is_data_loaded: bool

    sig_load_data_finished: Signal = Signal()
    sig_compute_field_finished: Signal = Signal()
    sig_preprocess_finished: Signal = Signal()
    sig_align_finished: Signal = Signal()
    sig_analysis_finished: Signal = Signal()
    sig_save_nexus_finished: Signal = Signal()
    sig_batch_finished: Signal = Signal()
    sig_preprocess_progress: Signal = Signal(int)
    sig_batch_progress: Signal = Signal(int)

    def __init__(self, *args, **kwargs) -> None:

        super().__init__()

        # Create the Processor object
        self.proc = self._type_processor(*args, **kwargs)
        if self.proc.is_nexus_file:
            self.is_data_loaded = True
        else:
            self.is_data_loaded = False

    @Slot()
    def load_data(self) -> None:
        """
        Load data.

        Optionally run the preprocessing, align the magnetic field and run the analysis.
        **kwargs are passed to the Processor `load_data()` method.
        """
        self.proc.load_data(**self._kwargs_load)
        self.is_data_loaded = True
        if self._after_load_preprocess:
            self.proc.preprocess(**self._kwargs_preprocess)
        if self._after_load_align:
            self.proc.align_field()
        if self._after_load_analysis:
            self.proc.run_analysis(**self._kwargs_analysis)

        self.sig_load_data_finished.emit()

    @Slot()
    def compute_field(self) -> None:
        """Compute magnetic field."""
        self.proc.compute_field()
        self.sig_compute_field_finished.emit()

    @Slot()
    def preprocess(self) -> None:
        """Run the preprocessing steps."""
        self.proc.preprocess(**self._kwargs_preprocess)
        self.sig_preprocess_finished.emit()

    @Slot()
    def align_field(self) -> None:
        """Align magnetic field on data."""
        self.proc.align_field()
        self.sig_align_finished.emit()

    @Slot()
    def run_analysis(self) -> None:
        """Run the analysis."""
        self.proc.run_analysis(**self._kwargs_analysis)
        self.sig_analysis_finished.emit()

    @Slot(str)
    def save_as_nexus(self, fname: str | Path) -> None:
        """Save data as NeXus."""
        self.proc.save(filename=fname)
        self.sig_save_nexus_finished.emit()

    @Slot(tuple)
    def batch_process(self, args: tuple) -> None:
        """Run batch processing."""
        pass
