"""general utils"""

from __future__ import annotations

from typing import Iterable
import h5py
import numpy
import os
from silx.io.utils import h5py_read_dataset
from silx.io.utils import open as hdf5_open
from nxtomo.io import to_target_rel_path

try:
    import tifffile  # noqa F401
except ImportError:
    has_tiffile = False
else:
    from tifffile import TiffFile

    has_tiffile = True

__all__ = ["cast_and_check_array_1D", "get_data_and_unit", "get_data"]


def cast_and_check_array_1D(array, array_name: str):
    """
    cast provided array to 1D

    :param array: array to be cast to 1D
    :param array_name: name of the array - used for log only
    """
    if not isinstance(array, (type(None), numpy.ndarray, Iterable)):
        raise TypeError(
            f"{array_name} is expected to be None, or an Iterable. Not {type(array)}"
        )
    if array is not None and not isinstance(array, numpy.ndarray):
        array = numpy.asarray(array)
    if array is not None and array.ndim > 1:
        raise ValueError(f"{array_name} is expected to be 0 or 1d not {array.ndim}")
    return array


def get_data_and_unit(file_path: str, data_path: str, default_unit):
    """
    return for an HDF5 dataset his value and his unit. If unit cannot be found then fallback on the 'default_unit'

    :param file_path: file path location of the HDF5Dataset to read
    :param data_path: data_path location of the HDF5Dataset to read
    :param default_unit: default unit to fall back if the dataset has no 'unit' or 'units' attribute
    """
    with hdf5_open(file_path) as h5f:
        if data_path in h5f and isinstance(h5f[data_path], h5py.Dataset):
            dataset = h5f[data_path]
            unit = None
            if "unit" in dataset.attrs:
                unit = dataset.attrs["unit"]
            elif "units" in dataset.attrs:
                unit = dataset.attrs["units"]
            else:
                unit = default_unit
            if hasattr(unit, "decode"):
                # handle Diamond dataset
                unit = unit.decode()
            return h5py_read_dataset(dataset), unit
        else:
            return None, default_unit


def get_data(file_path: str, data_path: str):
    """
    proxy to h5py_read_dataset, handling use case 'data_path' not present in the file.
    In this case return None

    :param file_path: file path location of the HDF5Dataset to read
    :param data_path: data_path location of the HDF5Dataset to read
    """
    with hdf5_open(file_path) as h5f:
        if data_path in h5f:
            return h5py_read_dataset(h5f[data_path])
        else:
            return None


def create_detector_dataset_from_tiff(
    tiff_files: tuple,
    external_dataset_group: h5py.Group,
    external_dataset_prefix="frame_",
    dtype=None,
    relative_link: bool = True,
) -> tuple[h5py.VirtualSource]:
    """
    create a series of externals datasets to tiff file (one per file) inside the 'external_dataset_group'

    :param tiff_files: set of files to create virtual sources to
    :param external_dataset_group: output HDF5 group. File must be accessible with write access (mode in 'w', 'a'...)
    :param dtype: expected dtype of all the tiff data. If not provided will be deduced from the first dataset.
    :param relative_link: if true create the link using relative link else use absolute path.

    .. warning::

        The most robust way to create a NXtomo should go by using relative link (in order to share it with the .tif files).
        Nevertheless there is today limitation on the resolution of the relative link with external dataset.
        (resolution is done according to the current working directory instead of the file...).
        The tomotools will handle it anyway but other software might not (like silx as this is a workaround and it should be handled at HDF5 level...)
        So be aware that those links might 'appear' broken when using relative link. This won't happen when using absolute links...
    """
    if not has_tiffile:
        raise RuntimeError("tiff file not installed")
    external_datasets = []

    # convert from local to ...

    for i_file, tiff_file in enumerate(tiff_files):
        with TiffFile(tiff_file, mode="r") as tif:
            fh = tif.filehandle
            for page in tif.pages:
                if dtype is not None:
                    assert dtype == page.dtype, "incoherent data type"
                dtype = page.dtype
                for index, (offset, bytecount) in enumerate(
                    zip(page.dataoffsets, page.databytecounts)
                ):
                    _ = fh.seek(offset)
                    data = fh.read(bytecount)
                    _, _, shape = page.decode(data, index, jpegtables=page.jpegtables)
                    if len(shape) == 4:
                        # don't know why but return it as 4D when 2D expected...
                        shape = shape[0:-1]
                    elif len(shape) == 2:
                        shape = 1, *shape

                    # move tiff file path to relative path
                    if relative_link:
                        external_file_path = to_target_rel_path(
                            file_path=tiff_file,
                            target_path=external_dataset_group.file.filename,
                        )
                    else:
                        external_file_path = os.path.abspath(tiff_file)

                    external_dataset = external_dataset_group.create_dataset(
                        name=f"{external_dataset_prefix}{str(i_file).zfill(6)}",
                        shape=shape,
                        dtype=dtype,
                        external=[(external_file_path, offset, bytecount)],
                    )
                    external_datasets.append(external_dataset)

    virtual_sources = []
    for i, ed in enumerate(external_datasets):
        vsource = h5py.VirtualSource(ed)
        virtual_sources.append(vsource)
    return tuple(virtual_sources)
