# -*- coding: utf-8 -*-
# Copyright (c) 2015, imageio contributors
# imageio is distributed under the terms of the (new) BSD License.

""" Storage of image data in tiff format.
"""

from __future__ import absolute_import, print_function, division

from .. import formats
from ..core import Format

import numpy as np

_tifffile = None  # Defer loading to lib() function.


def load_lib():
    global _tifffile
    try:
        import tifffile as _tifffile
    except ImportError:
        from . import _tifffile
    return _tifffile


TIFF_FORMATS = ('.tif', '.tiff', '.stk', '.lsm')
WRITE_METADATA_KEYS = ('photometric', 'planarconfig', 'resolution',
                       'description', 'compress', 'volume', 'writeshape',
                       'extratags')
READ_METADATA_KEYS = ('planar_configuration', 'is_fluoview', 'is_nih',
                      'is_contig', 'is_micromanager', 'is_ome', 'is_lsm'
                      'is_palette', 'is_reduced', 'is_rgb', 'is_sgi',
                      'is_shaped', 'is_stk', 'is_tiled', 'is_mdgel'
                      'resolution_unit', 'compression', 'is_mediacy',
                      'orientation')


class TiffFormat(Format):

    """ Provides support for a wide range of Tiff images.

    Parameters for reading
    ----------------------
    offset : int
        Optional start position of embedded file. By default this is
        the current file position.
    size : int
        Optional size of embedded file. By default this is the number
        of bytes from the 'offset' to the end of the file.
    multifile : bool
        If True (default), series may include pages from multiple files.
        Currently applies to OME-TIFF only.
    multifile_close : bool
        If True (default), keep the handles of other files in multifile
        series closed. This is inefficient when few files refer to
        many pages. If False, the C runtime may run out of resources.

    Parameters for saving
    ---------------------
    bigtiff : bool
        If True, the BigTIFF format is used.
    byteorder : {'<', '>'}
        The endianness of the data in the file.
        By default this is the system's native byte order.
    software : str
        Name of the software used to create the image.
        Saved with the first page only.

    Metadata for reading
    --------------------
    planar_configuration : {'contig', 'planar'}
        Specifies if samples are stored contiguous or in separate planes.
        By default this setting is inferred from the data shape.
        'contig': last dimension contains samples.
        'planar': third last dimension contains samples.
    resolution_unit : (float, float) or ((int, int), (int, int))
        X and Y resolution in dots per inch as float or rational numbers.
    compression : int
        Values from 0 to 9 indicating the level of zlib compression.
        If 0, data is uncompressed.
    orientation : {'top_left', 'bottom_right', ...}
        Oriented of image array.
    is_rgb : bool
        True if page contains a RGB image.
    is_contig : bool
        True if page contains a contiguous image.
    is_tiled : bool
        True if page contains tiled image.
    is_palette : bool
        True if page contains a palette-colored image and not OME or STK.
    is_reduced : bool
        True if page is a reduced image of another image.
    is_shaped : bool
        True if page contains shape in image_description tag.
    is_fluoview : bool
        True if page contains FluoView MM_STAMP tag.
    is_nih : bool
        True if page contains NIH image header.
    is_micromanager : bool
        True if page contains Micro-Manager metadata.
    is_ome : bool
        True if page contains OME-XML in image_description tag.
    is_sgi : bool
        True if page contains SGI image and tile depth tags.
    is_stk : bool
        True if page contains UIC2Tag tag.
    is_mdgel : bool
        True if page contains md_file_tag tag.
    is_mediacy : bool
        True if page contains Media Cybernetics Id tag.
    is_stk : bool
        True if page contains UIC2Tag tag.
    is_lsm : bool
        True if page contains LSM CZ_LSM_INFO tag.

    Metadata for writing
    --------------------
    photometric : {'minisblack', 'miniswhite', 'rgb'}
        The color space of the image data.
        By default this setting is inferred from the data shape.
    planarconfig : {'contig', 'planar'}
        Specifies if samples are stored contiguous or in separate planes.
        By default this setting is inferred from the data shape.
        'contig': last dimension contains samples.
        'planar': third last dimension contains samples.
    resolution : (float, float) or ((int, int), (int, int))
        X and Y resolution in dots per inch as float or rational numbers.
    description : str
        The subject of the image. Saved with the first page only.
    compress : int
        Values from 0 to 9 controlling the level of zlib compression.
        If 0, data are written uncompressed (default).
    volume : bool
        If True, volume data are stored in one tile (if applicable) using
        the SGI image_depth and tile_depth tags.
        Image width and depth must be multiple of 16.
        Few software can read this format, e.g. MeVisLab.
    writeshape : bool
        If True, write the data shape to the image_description tag
        if necessary and no other description is given.
    extratags: sequence of tuples
        Additional tags as [(code, dtype, count, value, writeonce)].

        code : int
            The TIFF tag Id.
        dtype : str
            Data type of items in 'value' in Python struct format.
            One of B, s, H, I, 2I, b, h, i, f, d, Q, or q.
        count : int
            Number of data values. Not used for string values.
        value : sequence
            'Count' values compatible with 'dtype'.
        writeonce : bool
            If True, the tag is written to the first page only.
    """

    def _can_read(self, request):
        # We support any kind of image data
        return request.filename.lower().endswith(self.extensions)

    def _can_write(self, request):
        # We support any kind of image data
        return request.filename.lower().endswith(self.extensions)

    # -- reader

    class Reader(Format.Reader):

        def _open(self, **kwargs):
            if not _tifffile:
                load_lib()
            self._tf = _tifffile.TiffFile(self.request.get_file(), **kwargs)

            # metadata is the same for all images
            self._meta = {}

        def _close(self):
            self._tf.close()

        def _get_length(self):
            return len(self._tf)

        def _get_data(self, index):
            # Get data
            if index < 0 or index >= len(self._tf):
                raise IndexError(
                    'Index out of range while reading from tiff file')
            im = self._tf[index].asarray()
            meta = self._meta or self._get_meta_data(index)
            # Return array and empty meta data
            return im, meta

        def _get_meta_data(self, index):
            page = self._tf[index or 0]
            for key in READ_METADATA_KEYS:
                try:
                    self._meta[key] = getattr(page, key)
                except Exception:
                    pass
            return self._meta

    # -- writer
    class Writer(Format.Writer):

        def _open(self, bigtiff=None, byteorder=None, software=None):
            if not _tifffile:
                load_lib()
            self._tf = _tifffile.TiffWriter(self.request.get_local_filename(),
                                            bigtiff, byteorder, software)
            self._meta = {}

        def _close(self):
            self._tf.close()

        def _append_data(self, im, meta):
            if meta:
                self.set_meta_data(meta)
            self._tf.save(np.asanyarray(im), **self._meta)

        def set_meta_data(self, meta):
            self._meta = {}
            for (key, value) in meta.items():
                if key in WRITE_METADATA_KEYS:
                    self._meta[key] = value


# Register
format = TiffFormat('tiff', "TIFF format", TIFF_FORMATS, 'iIvV')
formats.add_format(format)
