# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the NiBabel package for the
#   copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
''' Tests for loader function '''
from __future__ import division, print_function, absolute_import
from os.path import join as pjoin
import shutil
from tempfile import mkdtemp
from ..externals.six import BytesIO

import numpy as np

# If we don't have scipy, then we cannot write SPM format files
try:
    import scipy.io
except ImportError:
    have_scipy = False
else:
    have_scipy = True


from .. import analyze as ana
from .. import spm99analyze as spm99
from .. import spm2analyze as spm2
from .. import nifti1 as ni1
from .. import loadsave as nils
from .. import (Nifti1Image, Nifti1Pair, MincImage, Spm2AnalyzeImage,
                Spm99AnalyzeImage, AnalyzeImage, class_map)

from ..tmpdirs import InTemporaryDirectory

from ..volumeutils import native_code, swapped_code

from numpy.testing import assert_array_equal, assert_array_almost_equal
from nose.tools import assert_true, assert_equal, assert_raises


def round_trip(img):
    # round trip a nifti single
    sio = BytesIO()
    img.file_map['image'].fileobj = sio
    img.to_file_map()
    img2 = Nifti1Image.from_file_map(img.file_map)
    return img2


def test_conversion():
    shape = (2, 4, 6)
    affine = np.diag([1, 2, 3, 1])
    for npt in np.float32, np.int16:
        data = np.arange(np.prod(shape), dtype=npt).reshape(shape)
        for r_class_def in class_map.values():
            r_class = r_class_def['class']
            img = r_class(data, affine)
            img.set_data_dtype(npt)
            for w_class_def in class_map.values():
                w_class = w_class_def['class']
                img2 = w_class.from_image(img)
                assert_array_equal(img2.get_data(), data)
                assert_array_equal(img2.get_affine(), affine)


def test_save_load_endian():
    shape = (2, 4, 6)
    affine = np.diag([1, 2, 3, 1])
    data = np.arange(np.prod(shape), dtype='f4').reshape(shape)
    # Native endian image
    img = Nifti1Image(data, affine)
    assert_equal(img.get_header().endianness, native_code)
    img2 = round_trip(img)
    assert_equal(img2.get_header().endianness, native_code)
    assert_array_equal(img2.get_data(), data)
    # byte swapped endian image
    bs_hdr = img.get_header().as_byteswapped()
    bs_img = Nifti1Image(data, affine, bs_hdr)
    assert_equal(bs_img.get_header().endianness, swapped_code)
    # of course the data is the same because it's not written to disk
    assert_array_equal(bs_img.get_data(), data)
    # Check converting to another image
    cbs_img = AnalyzeImage.from_image(bs_img)
    # this will make the header native by doing the header conversion
    cbs_hdr = cbs_img.get_header()
    assert_equal(cbs_hdr.endianness, native_code)
    # and the byte order follows it back into another image
    cbs_img2 = Nifti1Image.from_image(cbs_img)
    cbs_hdr2 = cbs_img2.get_header()
    assert_equal(cbs_hdr2.endianness, native_code)
    # Try byteswapped round trip
    bs_img2 = round_trip(bs_img)
    bs_data2 = bs_img2.get_data()
    # now the data dtype was swapped endian, so the read data is too
    assert_equal(bs_data2.dtype.byteorder, swapped_code)
    assert_equal(bs_img2.get_header().endianness, swapped_code)
    assert_array_equal(bs_data2, data)
    # Now mix up byteswapped data and non-byteswapped header
    mixed_img = Nifti1Image(bs_data2, affine)
    assert_equal(mixed_img.get_header().endianness, native_code)
    m_img2 = round_trip(mixed_img)
    assert_equal(m_img2.get_header().endianness, native_code)
    assert_array_equal(m_img2.get_data(), data)


def test_save_load():
    shape = (2, 4, 6)
    npt = np.float32
    data = np.arange(np.prod(shape), dtype=npt).reshape(shape)
    affine = np.diag([1, 2, 3, 1])
    affine[:3,3] = [3,2,1]
    img = ni1.Nifti1Image(data, affine)
    img.set_data_dtype(npt)
    with InTemporaryDirectory() as pth:
        nifn = 'an_image.nii'
        sifn = 'another_image.img'
        ni1.save(img, nifn)
        re_img = nils.load(nifn)
        assert_true(isinstance(re_img, ni1.Nifti1Image))
        assert_array_equal(re_img.get_data(), data)
        assert_array_equal(re_img.get_affine(), affine)
        # These and subsequent del statements are to prevent confusing
        # windows errors when trying to open files or delete the
        # temporary directory. 
        del re_img
        if have_scipy: # skip we we cannot read .mat files
            spm2.save(img, sifn)
            re_img2 = nils.load(sifn)
            assert_true(isinstance(re_img2, spm2.Spm2AnalyzeImage))
            assert_array_equal(re_img2.get_data(), data)
            assert_array_equal(re_img2.get_affine(), affine)
            del re_img2
            spm99.save(img, sifn)
            re_img3 = nils.load(sifn)
            assert_true(isinstance(re_img3,
                                         spm99.Spm99AnalyzeImage))
            assert_array_equal(re_img3.get_data(), data)
            assert_array_equal(re_img3.get_affine(), affine)
            ni1.save(re_img3, nifn)
            del re_img3
        re_img = nils.load(nifn)
        assert_true(isinstance(re_img, ni1.Nifti1Image))
        assert_array_equal(re_img.get_data(), data)
        assert_array_equal(re_img.get_affine(), affine)
        del re_img


def test_two_to_one():
    # test going from two to one file in save
    shape = (2, 4, 6)
    npt = np.float32
    data = np.arange(np.prod(shape), dtype=npt).reshape(shape)
    affine = np.diag([1, 2, 3, 1])
    affine[:3,3] = [3,2,1]
    # single file format
    img = ni1.Nifti1Image(data, affine)
    assert_equal(img.get_header()['magic'], b'n+1')
    str_io = BytesIO()
    img.file_map['image'].fileobj = str_io
    # check that the single format vox offset is set correctly
    img.to_file_map()
    assert_equal(img.get_header()['magic'], b'n+1')
    assert_equal(img.get_header()['vox_offset'], 352)
    # make a new pair image, with the single image header
    pimg = ni1.Nifti1Pair(data, affine, img.get_header())
    isio = BytesIO()
    hsio = BytesIO()
    pimg.file_map['image'].fileobj = isio
    pimg.file_map['header'].fileobj = hsio
    pimg.to_file_map()
    # the offset remains the same
    assert_equal(pimg.get_header()['magic'], b'ni1')
    assert_equal(pimg.get_header()['vox_offset'], 352)
    assert_array_equal(pimg.get_data(), data)
    # same for from_image, going from single image to pair format
    ana_img = ana.AnalyzeImage.from_image(img)
    assert_equal(ana_img.get_header()['vox_offset'], 352)
    # back to the single image, save it again to a stringio
    str_io = BytesIO()
    img.file_map['image'].fileobj = str_io
    img.to_file_map()
    assert_equal(img.get_header()['vox_offset'], 352)
    aimg = ana.AnalyzeImage.from_image(img)
    assert_equal(aimg.get_header()['vox_offset'], 352)
    aimg = spm99.Spm99AnalyzeImage.from_image(img)
    assert_equal(aimg.get_header()['vox_offset'], 352)
    aimg = spm2.Spm2AnalyzeImage.from_image(img)
    assert_equal(aimg.get_header()['vox_offset'], 352)
    nfimg = ni1.Nifti1Pair.from_image(img)
    assert_equal(nfimg.get_header()['vox_offset'], 352)
    # now set the vox offset directly
    hdr = nfimg.get_header()
    hdr['vox_offset'] = 0
    assert_equal(nfimg.get_header()['vox_offset'], 0)
    # check it gets properly set by the nifti single image
    nfimg = ni1.Nifti1Image.from_image(img)
    assert_equal(nfimg.get_header()['vox_offset'], 352)


def test_negative_load_save():
    shape = (1,2,5)
    data = np.arange(10).reshape(shape) - 10.0
    affine = np.eye(4)
    hdr = ni1.Nifti1Header()
    hdr.set_data_dtype(np.int16)
    img = Nifti1Image(data, affine, hdr)
    str_io = BytesIO()
    img.file_map['image'].fileobj = str_io
    img.to_file_map()
    str_io.seek(0)
    re_img = Nifti1Image.from_file_map(img.file_map)
    assert_array_almost_equal(re_img.get_data(), data, 4)


def test_filename_save():
    # This is to test the logic in the load and save routines, relating
    # extensions to filetypes
    # Tuples of class, ext, loadedclass
    inklass_ext_loadklasses = (
        (Nifti1Image, '.nii', Nifti1Image),
        (Nifti1Image, '.img', Nifti1Pair),
        (MincImage, '.nii', Nifti1Image),
        (MincImage, '.img', Nifti1Pair),
        (Spm2AnalyzeImage, '.nii', Nifti1Image),
        (Spm2AnalyzeImage, '.img', Spm2AnalyzeImage),
        (Spm99AnalyzeImage, '.nii', Nifti1Image),
        (Spm99AnalyzeImage, '.img', Spm2AnalyzeImage),
        (AnalyzeImage, '.nii', Nifti1Image),
        (AnalyzeImage, '.img', Spm2AnalyzeImage),
    )
    shape = (2, 4, 6)
    affine = np.diag([1, 2, 3, 1])
    data = np.arange(np.prod(shape), dtype='f4').reshape(shape)
    for inklass, out_ext, loadklass in inklass_ext_loadklasses:
        if not have_scipy:
            # We can't load a SPM analyze type without scipy.  These types have
            # a 'mat' file (the type we can't load)
            if ('mat', '.mat') in loadklass.files_types:
                continue
        img = inklass(data, affine)
        try:
            pth = mkdtemp()
            fname = pjoin(pth, 'image' + out_ext)
            nils.save(img, fname)
            rt_img = nils.load(fname)
            assert_array_almost_equal(rt_img.get_data(), data)
            assert_true(type(rt_img) is loadklass)
            # delete image to allow file close.  Otherwise windows
            # raises an error when trying to delete the directory
            del rt_img
        finally:
            shutil.rmtree(pth)
