"""
Module for handling an `NXbeam <https://manual.nexusformat.org/classes/base_classes/NXbeam.html>`_.
"""

import logging
from functools import partial
from operator import is_not

import pint
from silx.utils.proxy import docstring

from nxtomo.nxobject.nxobject import NXobject
from nxtomo.nxobject.utils.decorator import check_dimensionality
from nxtomo.paths.nxtomo import get_paths as get_nexus_paths
from nxtomo.utils import get_quantity

_logger = logging.getLogger(__name__)

_ureg = pint.get_application_registry()

__all__ = [
    "NXbeam",
]


class NXbeam(NXobject):
    def __init__(self, node_name="beam", parent: NXobject | None = None) -> None:
        """
        Representation of `NeXus NXbeam <https://manual.nexusformat.org/classes/base_classes/NXbeam.html>`_.

        :param node_name: name of the monitor in the hierarchy.
        :param parent: parent in the NeXus hierarchy.
        """
        super().__init__(node_name=node_name, parent=parent)
        self._set_freeze(False)
        self._incident_energy = None
        self._set_freeze(True)

    @property
    def incident_energy(self) -> pint.Quantity | None:
        return self._incident_energy

    @incident_energy.setter
    @check_dimensionality(expected_dimension="[energy]")
    def incident_energy(self, energy: pint.Quantity | None) -> None:
        if energy is None:
            self._incident_energy = None
        elif isinstance(energy, pint.Quantity):
            self._incident_energy = energy.to(_ureg.keV)
        else:
            raise TypeError(
                f"energy is expected to be a pint.Quantity or None. Not {type(energy)}"
            )

    def _load(
        self, file_path: str, data_path: str, nexus_version: float | None
    ) -> NXobject:
        nexus_paths = get_nexus_paths(nexus_version)
        nexus_beam_paths = nexus_paths.nx_beam_paths
        self.incident_energy = get_quantity(
            file_path=file_path,
            data_path="/".join([data_path, nexus_beam_paths.INCIDENT_ENERGY]),
            default_unit=_ureg.keV,
        )

    @docstring(NXobject)
    def to_nx_dict(
        self,
        nexus_path_version: float | None = None,
    ) -> dict:
        """
        Dump the NXbeam to a dictionary.

        :param nexus_path_version: Nexus version number.
        """
        nx_dict = {}
        nexus_paths = get_nexus_paths(nexus_path_version)
        nexus_beam_paths = nexus_paths.nx_beam_paths
        if self.incident_energy is not None:
            path_energy = f"{self.path}/{nexus_beam_paths.INCIDENT_ENERGY}"

            nx_dict[path_energy] = self.incident_energy.magnitude
            nx_dict["@".join([path_energy, "units"])] = (
                f"{self.incident_energy.units:~}"
            )
            nx_dict["@".join([self.path, "NX_class"])] = "NXbeam"

        return nx_dict

    @staticmethod
    @docstring(NXobject)
    def concatenate(nx_objects: tuple, node_name="beam"):
        # filter None obj
        nx_objects = tuple(filter(partial(is_not, None), nx_objects))
        if len(nx_objects) == 0:
            return None
        # warning: later we make the assumption that nx_objects contains at least one element
        for nx_obj in nx_objects:
            if not isinstance(nx_obj, NXbeam):
                raise TypeError("Cannot concatenate non NXsource object")

        nx_beam = NXbeam(node_name=node_name)
        nx_beam.incident_energy = nx_objects[0].incident_energy
        _logger.info(
            f"Take incident energy of the first NXbeam {nx_objects[0].node_name}"
        )
        return nx_beam
