import io
import struct

from helper import unittest, PillowTestCase, hopper

from PIL import Image, TiffImagePlugin, TiffTags
from PIL.TiffImagePlugin import _limit_rational, IFDRational

tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()}


class TestFileTiffMetadata(PillowTestCase):

    def test_rt_metadata(self):
        """ Test writing arbitrary metadata into the tiff image directory
            Use case is ImageJ private tags, one numeric, one arbitrary
            data.  https://github.com/python-pillow/Pillow/issues/291
            """

        img = hopper()

        # Behaviour change: re #1416
        # Pre ifd rewrite, ImageJMetaData was being written as a string(2),
        # Post ifd rewrite, it's defined as arbitrary bytes(7). It should
        # roundtrip with the actual bytes, rather than stripped text
        # of the premerge tests.
        #
        # For text items, we still have to decode('ascii','replace') because
        # the tiff file format can't take 8 bit bytes in that field.

        basetextdata = "This is some arbitrary metadata for a text field"
        bindata = basetextdata.encode('ascii') + b" \xff"
        textdata = basetextdata + " " + chr(255)
        reloaded_textdata = basetextdata + " ?"
        floatdata = 12.345
        doubledata = 67.89
        info = TiffImagePlugin.ImageFileDirectory()

        ImageJMetaData = tag_ids['ImageJMetaData']
        ImageJMetaDataByteCounts = tag_ids['ImageJMetaDataByteCounts']
        ImageDescription = tag_ids['ImageDescription']

        info[ImageJMetaDataByteCounts] = len(bindata)
        info[ImageJMetaData] = bindata
        info[tag_ids['RollAngle']] = floatdata
        info.tagtype[tag_ids['RollAngle']] = 11
        info[tag_ids['YawAngle']] = doubledata
        info.tagtype[tag_ids['YawAngle']] = 12

        info[ImageDescription] = textdata

        f = self.tempfile("temp.tif")

        img.save(f, tiffinfo=info)

        loaded = Image.open(f)

        self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),))
        self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),))

        self.assertEqual(loaded.tag[ImageJMetaData], bindata)
        self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)

        self.assertEqual(loaded.tag[ImageDescription], (reloaded_textdata,))
        self.assertEqual(loaded.tag_v2[ImageDescription], reloaded_textdata)

        loaded_float = loaded.tag[tag_ids['RollAngle']][0]
        self.assertAlmostEqual(loaded_float, floatdata, places=5)
        loaded_double = loaded.tag[tag_ids['YawAngle']][0]
        self.assertAlmostEqual(loaded_double, doubledata)

        # check with 2 element ImageJMetaDataByteCounts, issue #2006

        info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8)
        img.save(f, tiffinfo=info)
        loaded = Image.open(f)

        self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8))
        self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8))

    def test_read_metadata(self):
        img = Image.open('Tests/images/hopper_g4.tif')

        self.assertEqual({'YResolution': IFDRational(4294967295, 113653537),
                          'PlanarConfiguration': 1,
                          'BitsPerSample': (1,),
                          'ImageLength': 128,
                          'Compression': 4,
                          'FillOrder': 1,
                          'RowsPerStrip': 128,
                          'ResolutionUnit': 3,
                          'PhotometricInterpretation': 0,
                          'PageNumber': (0, 1),
                          'XResolution': IFDRational(4294967295, 113653537),
                          'ImageWidth': 128,
                          'Orientation': 1,
                          'StripByteCounts': (1968,),
                          'SamplesPerPixel': 1,
                          'StripOffsets': (8,)
                          }, img.tag_v2.named())

        self.assertEqual({'YResolution': ((4294967295, 113653537),),
                          'PlanarConfiguration': (1,),
                          'BitsPerSample': (1,),
                          'ImageLength': (128,),
                          'Compression': (4,),
                          'FillOrder': (1,),
                          'RowsPerStrip': (128,),
                          'ResolutionUnit': (3,),
                          'PhotometricInterpretation': (0,),
                          'PageNumber': (0, 1),
                          'XResolution': ((4294967295, 113653537),),
                          'ImageWidth': (128,),
                          'Orientation': (1,),
                          'StripByteCounts': (1968,),
                          'SamplesPerPixel': (1,),
                          'StripOffsets': (8,)
                          }, img.tag.named())

    def test_write_metadata(self):
        """ Test metadata writing through the python code """
        img = Image.open('Tests/images/hopper.tif')

        f = self.tempfile('temp.tiff')
        img.save(f, tiffinfo=img.tag)

        loaded = Image.open(f)

        original = img.tag_v2.named()
        reloaded = loaded.tag_v2.named()

        for k, v in original.items():
            if isinstance(v, IFDRational):
                original[k] = IFDRational(*_limit_rational(v, 2**31))
            if isinstance(v, tuple) and isinstance(v[0], IFDRational):
                original[k] = tuple([IFDRational(
                                     *_limit_rational(elt, 2**31)) for elt in v])

        ignored = ['StripByteCounts', 'RowsPerStrip',
                   'PageNumber', 'StripOffsets']

        for tag, value in reloaded.items():
            if tag in ignored:
                continue
            if (isinstance(original[tag], tuple)
               and isinstance(original[tag][0], IFDRational)):
                # Need to compare element by element in the tuple,
                # not comparing tuples of object references
                self.assert_deep_equal(original[tag],
                                       value,
                                       "%s didn't roundtrip, %s, %s" %
                                       (tag, original[tag], value))
            else:
                self.assertEqual(original[tag],
                                 value,
                                 "%s didn't roundtrip, %s, %s" %
                                 (tag, original[tag], value))

        for tag, value in original.items():
            if tag not in ignored:
                self.assertEqual(
                    value, reloaded[tag], "%s didn't roundtrip" % tag)

    def test_no_duplicate_50741_tag(self):
        self.assertEqual(tag_ids['MakerNoteSafety'], 50741)
        self.assertEqual(tag_ids['BestQualityScale'], 50780)

    def test_empty_metadata(self):
        f = io.BytesIO(b'II*\x00\x08\x00\x00\x00')
        head = f.read(8)
        info = TiffImagePlugin.ImageFileDirectory(head)
        try:
            self.assert_warning(UserWarning, info.load, f)
        except struct.error:
            self.fail("Should not be struct errors there.")

    def test_iccprofile(self):
        # https://github.com/python-pillow/Pillow/issues/1462
        im = Image.open('Tests/images/hopper.iccprofile.tif')
        out = self.tempfile('temp.tiff')

        im.save(out)
        reloaded = Image.open(out)
        self.assertNotIsInstance(im.info['icc_profile'], tuple)
        self.assertEqual(im.info['icc_profile'], reloaded.info['icc_profile'])

    def test_iccprofile_binary(self):
        # https://github.com/python-pillow/Pillow/issues/1526
        # We should be able to load this, but probably won't be able to save it.

        im = Image.open('Tests/images/hopper.iccprofile_binary.tif')
        self.assertEqual(im.tag_v2.tagtype[34675], 1)
        self.assertTrue(im.info['icc_profile'])

    def test_iccprofile_save_png(self):
        im = Image.open('Tests/images/hopper.iccprofile.tif')
        outfile = self.tempfile('temp.png')
        im.save(outfile)

    def test_iccprofile_binary_save_png(self):
        im = Image.open('Tests/images/hopper.iccprofile_binary.tif')
        outfile = self.tempfile('temp.png')
        im.save(outfile)

    def test_exif_div_zero(self):
        im = hopper()
        info = TiffImagePlugin.ImageFileDirectory_v2()
        info[41988] = TiffImagePlugin.IFDRational(0, 0)

        out = self.tempfile('temp.tiff')
        im.save(out, tiffinfo=info, compression='raw')

        reloaded = Image.open(out)
        self.assertEqual(0, reloaded.tag_v2[41988].numerator)
        self.assertEqual(0, reloaded.tag_v2[41988].denominator)

    def test_expty_values(self):
        data = io.BytesIO(
            b'II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00'
            b'\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00'
            b'\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a '
            b'text\x00\x00')
        head = data.read(8)
        info = TiffImagePlugin.ImageFileDirectory_v2(head)
        info.load(data)
        try:
            info = dict(info)
        except ValueError:
            self.fail("Should not be struct value error there.")
        self.assertIn(33432, info)

    def test_PhotoshopInfo(self):
        im = Image.open('Tests/images/issue_2278.tif')

        self.assertIsInstance(im.tag_v2[34377], bytes)
        out = self.tempfile('temp.tiff')
        im.save(out)
        reloaded = Image.open(out)
        self.assertIsInstance(reloaded.tag_v2[34377], bytes)

    def test_too_many_entries(self):
        ifd = TiffImagePlugin.ImageFileDirectory_v2()

        #    277: ("SamplesPerPixel", SHORT, 1),
        ifd._tagdata[277] = struct.pack('hh', 4, 4)
        ifd.tagtype[277] = TiffTags.SHORT

        try:
            self.assert_warning(UserWarning, lambda: ifd[277])
        except ValueError:
            self.fail("Invalid Metadata count should not cause a Value Error.")


if __name__ == '__main__':
    unittest.main()
