## taken from: https://github.com/yarikoptic/nitest-balls1/blob/2cd07d86e2cc2d3c612d5d4d659daccd7a58f126/NIFTI/T1.nii.gz

from pathlib import Path

import pyarrow as pa
import pytest

from datasets import Dataset, Features, Nifti, load_dataset
from src.datasets.features.nifti import encode_nibabel_image

from ..utils import require_nibabel


@require_nibabel
@pytest.mark.parametrize("nifti_file", ["test_nifti.nii", "test_nifti.nii.gz"])
@pytest.mark.parametrize(
    "build_example",
    [
        lambda nifti_path: nifti_path,
        lambda nifti_path: Path(nifti_path),
        lambda nifti_path: open(nifti_path, "rb").read(),
        lambda nifti_path: {"path": nifti_path},
        lambda nifti_path: {"path": nifti_path, "bytes": None},
        lambda nifti_path: {"path": nifti_path, "bytes": open(nifti_path, "rb").read()},
        lambda nifti_path: {"path": None, "bytes": open(nifti_path, "rb").read()},
        lambda nifti_path: {"bytes": open(nifti_path, "rb").read()},
    ],
)
def test_nifti_feature_encode_example(shared_datadir, nifti_file, build_example):
    import nibabel

    nifti_path = str(shared_datadir / nifti_file)
    nifti = Nifti()
    encoded_example = nifti.encode_example(build_example(nifti_path))
    assert isinstance(encoded_example, dict)
    assert encoded_example.keys() == {"bytes", "path"}
    assert encoded_example["bytes"] is not None or encoded_example["path"] is not None
    decoded_example = nifti.decode_example(encoded_example)
    assert isinstance(decoded_example, nibabel.nifti1.Nifti1Image)


@require_nibabel
@pytest.mark.parametrize("nifti_file", ["test_nifti.nii", "test_nifti.nii.gz"])
def test_dataset_with_nifti_feature(shared_datadir, nifti_file):
    import nibabel

    nifti_path = str(shared_datadir / nifti_file)
    data = {"nifti": [nifti_path]}
    features = Features({"nifti": Nifti()})
    dset = Dataset.from_dict(data, features=features)
    item = dset[0]
    assert item.keys() == {"nifti"}
    assert isinstance(item["nifti"], nibabel.nifti1.Nifti1Image)
    batch = dset[:1]
    assert len(batch) == 1
    assert batch.keys() == {"nifti"}
    assert isinstance(batch["nifti"], list) and all(
        isinstance(item, nibabel.nifti1.Nifti1Image) for item in batch["nifti"]
    )
    column = dset["nifti"]
    assert len(column) == 1
    assert all(isinstance(item, nibabel.nifti1.Nifti1Image) for item in column)

    # from bytes
    with open(nifti_path, "rb") as f:
        data = {"nifti": [f.read()]}
    dset = Dataset.from_dict(data, features=features)
    item = dset[0]
    assert item.keys() == {"nifti"}
    assert isinstance(item["nifti"], nibabel.nifti1.Nifti1Image)


@require_nibabel
def test_encode_nibabel_image(shared_datadir):
    import nibabel

    nifti_path = str(shared_datadir / "test_nifti.nii")
    img = nibabel.load(nifti_path)
    encoded_example = encode_nibabel_image(img)
    nifti = Nifti()
    assert isinstance(encoded_example, dict)
    assert encoded_example.keys() == {"bytes", "path"}
    assert encoded_example["path"] is not None and encoded_example["bytes"] is None
    decoded_example = nifti.decode_example(encoded_example)
    assert isinstance(decoded_example, nibabel.nifti1.Nifti1Image)

    # test bytes only
    img.file_map = None
    encoded_example_bytes = encode_nibabel_image(img)
    assert isinstance(encoded_example_bytes, dict)
    assert encoded_example_bytes["bytes"] is not None and encoded_example_bytes["path"] is None
    # this cannot be converted back from bytes (yet)


@require_nibabel
def test_embed_storage(shared_datadir):
    from io import BytesIO

    import nibabel as nib

    nifti_path = str(shared_datadir / "test_nifti.nii")
    img = nib.load(nifti_path)
    nifti = Nifti()

    bytes_array = pa.array([None], type=pa.binary())
    path_array = pa.array([nifti_path], type=pa.string())
    storage = pa.StructArray.from_arrays([bytes_array, path_array], ["bytes", "path"])

    embedded_storage = nifti.embed_storage(storage)

    embedded_bytes = embedded_storage[0]["bytes"].as_py()

    bio = BytesIO(embedded_bytes)
    fh = nib.FileHolder(fileobj=bio)
    nifti_img = nib.Nifti1Image.from_file_map({"header": fh, "image": fh})

    assert embedded_bytes is not None
    assert nifti_img.header == img.header
    assert (nifti_img.affine == img.affine).all()
    assert (nifti_img.get_fdata() == img.get_fdata()).all()


@require_nibabel
def test_load_zipped_file_locally(shared_datadir):
    import nibabel as nib

    nifti_path = str(shared_datadir / "test_nifti.nii.gz")

    ds = load_dataset("niftifolder", data_files=nifti_path)
    assert isinstance(ds["train"][0]["nifti"], nib.nifti1.Nifti1Image)


@require_nibabel
def test_nifti_lazy_loading(shared_datadir):
    import nibabel as nib
    import numpy as np

    nifti_path = str(shared_datadir / "test_nifti.nii.gz")
    nifti = Nifti()
    encoded_example = nifti.encode_example(nifti_path)
    decoded_example = nifti.decode_example(encoded_example)

    # Verify that the data object is an ArrayProxy (lazy) and not a numpy array (dense)
    assert nib.is_proxy(decoded_example.dataobj)
    assert not isinstance(decoded_example.dataobj, np.ndarray)

    # Verify that we can still access the data
    data = decoded_example.get_fdata()
    assert data.shape == (80, 80, 10)
