import copy
import numbers
import struct

from ._common import *
from ._exif import *


TIFF_HEADER_LENGTH = 8


def dump(exif_dict_original):
    """
    py:function:: piexif.load(data)

    Return exif as bytes.

    :param dict exif: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes})
    :return: Exif
    :rtype: bytes
    """
    exif_dict = copy.deepcopy(exif_dict_original)
    header = b"Exif\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08"
    exif_is = False
    gps_is = False
    interop_is = False
    first_is = False

    if "0th" in exif_dict:
        zeroth_ifd = exif_dict["0th"]
    else:
        zeroth_ifd = {}

    if (("Exif" in exif_dict) and len(exif_dict["Exif"]) or
          ("Interop" in exif_dict) and len(exif_dict["Interop"]) ):
        zeroth_ifd[ImageIFD.ExifTag] = 1
        exif_is = True
        exif_ifd = exif_dict["Exif"]
        if ("Interop" in exif_dict) and len(exif_dict["Interop"]):
            exif_ifd[ExifIFD. InteroperabilityTag] = 1
            interop_is = True
            interop_ifd = exif_dict["Interop"]
        elif ExifIFD. InteroperabilityTag in exif_ifd:
            exif_ifd.pop(ExifIFD.InteroperabilityTag)
    elif ImageIFD.ExifTag in zeroth_ifd:
        zeroth_ifd.pop(ImageIFD.ExifTag)

    if ("GPS" in exif_dict) and len(exif_dict["GPS"]):
        zeroth_ifd[ImageIFD.GPSTag] = 1
        gps_is = True
        gps_ifd = exif_dict["GPS"]
    elif ImageIFD.GPSTag in zeroth_ifd:
        zeroth_ifd.pop(ImageIFD.GPSTag)

    if (("1st" in exif_dict) and
            ("thumbnail" in exif_dict) and
            (exif_dict["thumbnail"] is not None)):
        first_is = True
        exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] = 1
        exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength] = 1
        first_ifd = exif_dict["1st"]

    zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0)
    zeroth_length = (len(zeroth_set[0]) + exif_is * 12 + gps_is * 12 + 4 +
                     len(zeroth_set[1]))

    if exif_is:
        exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length)
        exif_length = len(exif_set[0]) + interop_is * 12 + len(exif_set[1])
    else:
        exif_bytes = b""
        exif_length = 0
    if gps_is:
        gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length)
        gps_bytes = b"".join(gps_set)
        gps_length = len(gps_bytes)
    else:
        gps_bytes = b""
        gps_length = 0
    if interop_is:
        offset = zeroth_length + exif_length + gps_length
        interop_set = _dict_to_bytes(interop_ifd, "Interop", offset)
        interop_bytes = b"".join(interop_set)
        interop_length = len(interop_bytes)
    else:
        interop_bytes = b""
        interop_length = 0
    if first_is:
        offset = zeroth_length + exif_length + gps_length + interop_length
        first_set = _dict_to_bytes(first_ifd, "1st", offset)
        thumbnail = _get_thumbnail(exif_dict["thumbnail"])
        thumbnail_max_size = 64000
        if len(thumbnail) > thumbnail_max_size:
            raise ValueError("Given thumbnail is too large. max 64kB")
    else:
        first_bytes = b""
    if exif_is:
        pointer_value = TIFF_HEADER_LENGTH + zeroth_length
        pointer_str = struct.pack(">I", pointer_value)
        key = ImageIFD.ExifTag
        key_str = struct.pack(">H", key)
        type_str = struct.pack(">H", TYPES.Long)
        length_str = struct.pack(">I", 1)
        exif_pointer = key_str + type_str + length_str + pointer_str
    else:
        exif_pointer = b""
    if gps_is:
        pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length
        pointer_str = struct.pack(">I", pointer_value)
        key = ImageIFD.GPSTag
        key_str = struct.pack(">H", key)
        type_str = struct.pack(">H", TYPES.Long)
        length_str = struct.pack(">I", 1)
        gps_pointer = key_str + type_str + length_str + pointer_str
    else:
        gps_pointer = b""
    if interop_is:
        pointer_value = (TIFF_HEADER_LENGTH +
                         zeroth_length + exif_length + gps_length)
        pointer_str = struct.pack(">I", pointer_value)
        key = ExifIFD.InteroperabilityTag
        key_str = struct.pack(">H", key)
        type_str = struct.pack(">H", TYPES.Long)
        length_str = struct.pack(">I", 1)
        interop_pointer = key_str + type_str + length_str + pointer_str
    else:
        interop_pointer = b""
    if first_is:
        pointer_value = (TIFF_HEADER_LENGTH + zeroth_length +
                         exif_length + gps_length + interop_length)
        first_ifd_pointer = struct.pack(">L", pointer_value)
        thumbnail_pointer = (pointer_value + len(first_set[0]) + 24 +
                             4 + len(first_set[1]))
        thumbnail_p_bytes = (b"\x02\x01\x00\x04\x00\x00\x00\x01" +
                             struct.pack(">L", thumbnail_pointer))
        thumbnail_length_bytes = (b"\x02\x02\x00\x04\x00\x00\x00\x01" +
                                  struct.pack(">L", len(thumbnail)))
        first_bytes = (first_set[0] + thumbnail_p_bytes +
                       thumbnail_length_bytes + b"\x00\x00\x00\x00" +
                       first_set[1] + thumbnail)
    else:
        first_ifd_pointer = b"\x00\x00\x00\x00"

    zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer +
                    first_ifd_pointer + zeroth_set[1])
    if exif_is:
        exif_bytes = exif_set[0] + interop_pointer + exif_set[1]

    return (header + zeroth_bytes + exif_bytes + gps_bytes +
            interop_bytes + first_bytes)


def _get_thumbnail(jpeg):
    segments = split_into_segments(jpeg)
    while (b"\xff\xe0" <= segments[1][0:2] <= b"\xff\xef"):
        segments.pop(1)
    thumbnail = b"".join(segments)
    return thumbnail


def _pack_byte(*args):
    return struct.pack("B" * len(args), *args)

def _pack_signed_byte(*args):
    return struct.pack("b" * len(args), *args)

def _pack_short(*args):
    return struct.pack(">" + "H" * len(args), *args)

def _pack_signed_short(*args):
    return struct.pack(">" + "h" * len(args), *args)

def _pack_long(*args):
    return struct.pack(">" + "L" * len(args), *args)

def _pack_slong(*args):
    return struct.pack(">" + "l" * len(args), *args)

def _pack_float(*args):
    return struct.pack(">" + "f" * len(args), *args)

def _pack_double(*args):
    return struct.pack(">" + "d" * len(args), *args)


def _value_to_bytes(raw_value, value_type, offset):
    four_bytes_over = b""
    value_str = b""

    if value_type == TYPES.Byte:
        length = len(raw_value)
        if length <= 4:
            value_str = (_pack_byte(*raw_value) +
                            b"\x00" * (4 - length))
        else:
            value_str = struct.pack(">I", offset)
            four_bytes_over = _pack_byte(*raw_value)
    elif value_type == TYPES.Short:
        length = len(raw_value)
        if length <= 2:
            value_str = (_pack_short(*raw_value) +
                            b"\x00\x00" * (2 - length))
        else:
            value_str = struct.pack(">I", offset)
            four_bytes_over = _pack_short(*raw_value)
    elif value_type == TYPES.Long:
        length = len(raw_value)
        if length <= 1:
            value_str = _pack_long(*raw_value)
        else:
            value_str = struct.pack(">I", offset)
            four_bytes_over = _pack_long(*raw_value)
    elif value_type == TYPES.SLong:
        length = len(raw_value)
        if length <= 1:
            value_str = _pack_slong(*raw_value)
        else:
            value_str = struct.pack(">I", offset)
            four_bytes_over = _pack_slong(*raw_value)
    elif value_type == TYPES.Ascii:
        try:
            new_value = raw_value.encode("latin1") + b"\x00"
        except:
            try:
                new_value = raw_value + b"\x00"
            except TypeError:
                raise ValueError("Got invalid type to convert.")
        length = len(new_value)
        if length > 4:
            value_str = struct.pack(">I", offset)
            four_bytes_over = new_value
        else:
            value_str = new_value + b"\x00" * (4 - length)
    elif value_type == TYPES.Rational:
        if isinstance(raw_value[0], numbers.Integral):
            length = 1
            num, den = raw_value
            new_value = struct.pack(">L", num) + struct.pack(">L", den)
        elif isinstance(raw_value[0], tuple):
            length = len(raw_value)
            new_value = b""
            for n, val in enumerate(raw_value):
                num, den = val
                new_value += (struct.pack(">L", num) +
                                struct.pack(">L", den))
        value_str = struct.pack(">I", offset)
        four_bytes_over = new_value
    elif value_type == TYPES.SRational:
        if isinstance(raw_value[0], numbers.Integral):
            length = 1
            num, den = raw_value
            new_value = struct.pack(">l", num) + struct.pack(">l", den)
        elif isinstance(raw_value[0], tuple):
            length = len(raw_value)
            new_value = b""
            for n, val in enumerate(raw_value):
                num, den = val
                new_value += (struct.pack(">l", num) +
                                struct.pack(">l", den))
        value_str = struct.pack(">I", offset)
        four_bytes_over = new_value
    elif value_type == TYPES.Undefined:
        length = len(raw_value)
        if length > 4:
            value_str = struct.pack(">I", offset)
            try:
                four_bytes_over = b"" + raw_value
            except TypeError:
                raise ValueError("Got invalid type to convert.")
        else:
            try:
                value_str = raw_value + b"\x00" * (4 - length)
            except TypeError:
                raise ValueError("Got invalid type to convert.")
    elif value_type == TYPES.SByte: # Signed Byte
        length = len(raw_value)
        if length <= 4:
            value_str = (_pack_signed_byte(*raw_value) +
                            b"\x00" * (4 - length))
        else:
            value_str = struct.pack(">I", offset)
            four_bytes_over = _pack_signed_byte(*raw_value)
    elif value_type == TYPES.SShort: # Signed Short
        length = len(raw_value)
        if length <= 2:
            value_str = (_pack_signed_short(*raw_value) +
                            b"\x00\x00" * (2 - length))
        else:
            value_str = struct.pack(">I", offset)
            four_bytes_over = _pack_signed_short(*raw_value)
    elif value_type == TYPES.Float:
        length = len(raw_value)
        if length <= 1:
            value_str = _pack_float(*raw_value)
        else:
            value_str = struct.pack(">I", offset)
            four_bytes_over = _pack_float(*raw_value)
    elif value_type == TYPES.DFloat: # Double
        length = len(raw_value)
        value_str = struct.pack(">I", offset)
        four_bytes_over = _pack_double(*raw_value)

    length_str = struct.pack(">I", length)
    return length_str, value_str, four_bytes_over

def _dict_to_bytes(ifd_dict, ifd, ifd_offset):
    tag_count = len(ifd_dict)
    entry_header = struct.pack(">H", tag_count)
    if ifd in ("0th", "1st"):
        entries_length = 2 + tag_count * 12 + 4
    else:
        entries_length = 2 + tag_count * 12
    entries = b""
    values = b""

    for n, key in enumerate(sorted(ifd_dict)):
        if (ifd == "0th") and (key in (ImageIFD.ExifTag, ImageIFD.GPSTag)):
            continue
        elif (ifd == "Exif") and (key == ExifIFD.InteroperabilityTag):
            continue
        elif (ifd == "1st") and (key in (ImageIFD.JPEGInterchangeFormat, ImageIFD.JPEGInterchangeFormatLength)):
            continue

        raw_value = ifd_dict[key]
        key_str = struct.pack(">H", key)
        value_type = TAGS[ifd][key]["type"]
        type_str = struct.pack(">H", value_type)
        four_bytes_over = b""

        if isinstance(raw_value, numbers.Integral) or isinstance(raw_value, float):
            raw_value = (raw_value,)
        offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + len(values)

        try:
            length_str, value_str, four_bytes_over = _value_to_bytes(raw_value,
                                                                     value_type,
                                                                     offset)
        except ValueError:
            raise ValueError(
                '"dump" got wrong type of exif value.\n' +
                '{0} in {1} IFD. Got as {2}.'.format(key, ifd, type(ifd_dict[key]))
            )

        entries += key_str + type_str + length_str + value_str
        values += four_bytes_over
    return (entry_header + entries, values)

[((22, 16, 22, 49), 'copy.deepcopy', 'copy.deepcopy', ({(22, 30, 22, 48): 'exif_dict_original'}, {}), '(exif_dict_original)', False, 'import copy\n'), ((303, 17, 303, 42), 'struct.pack', 'struct.pack', ({(303, 29, 303, 33): '""">I"""', (303, 35, 303, 41): 'length'}, {}), "('>I', length)", False, 'import struct\n'), ((308, 19, 308, 47), 'struct.pack', 'struct.pack', ({(308, 31, 308, 35): '""">H"""', (308, 37, 308, 46): 'tag_count'}, {}), "('>H', tag_count)", False, 'import struct\n'), ((99, 22, 99, 54), 'struct.pack', 'struct.pack', ({(99, 34, 99, 38): '""">I"""', (99, 40, 99, 53): 'pointer_value'}, {}), "('>I', pointer_value)", False, 'import struct\n'), ((101, 18, 101, 40), 'struct.pack', 'struct.pack', ({(101, 30, 101, 34): '""">H"""', (101, 36, 101, 39): 'key'}, {}), "('>H', key)", False, 'import struct\n'), ((102, 19, 102, 48), 'struct.pack', 'struct.pack', ({(102, 31, 102, 35): '""">H"""', (102, 37, 102, 47): 'TYPES.Long'}, {}), "('>H', TYPES.Long)", False, 'import struct\n'), ((103, 21, 103, 41), 'struct.pack', 'struct.pack', ({(103, 33, 103, 37): '""">I"""', (103, 39, 103, 40): '1'}, {}), "('>I', 1)", False, 'import struct\n'), ((109, 22, 109, 54), 'struct.pack', 'struct.pack', ({(109, 34, 109, 38): '""">I"""', (109, 40, 109, 53): 'pointer_value'}, {}), "('>I', pointer_value)", False, 'import struct\n'), ((111, 18, 111, 40), 'struct.pack', 'struct.pack', ({(111, 30, 111, 34): '""">H"""', (111, 36, 111, 39): 'key'}, {}), "('>H', key)", False, 'import struct\n'), ((112, 19, 112, 48), 'struct.pack', 'struct.pack', ({(112, 31, 112, 35): '""">H"""', (112, 37, 112, 47): 'TYPES.Long'}, {}), "('>H', TYPES.Long)", False, 'import struct\n'), ((113, 21, 113, 41), 'struct.pack', 'struct.pack', ({(113, 33, 113, 37): '""">I"""', (113, 39, 113, 40): '1'}, {}), "('>I', 1)", False, 'import struct\n'), ((120, 22, 120, 54), 'struct.pack', 'struct.pack', ({(120, 34, 120, 38): '""">I"""', (120, 40, 120, 53): 'pointer_value'}, {}), "('>I', pointer_value)", False, 'import struct\n'), ((122, 18, 122, 40), 'struct.pack', 'struct.pack', ({(122, 30, 122, 34): '""">H"""', (122, 36, 122, 39): 'key'}, {}), "('>H', key)", False, 'import struct\n'), ((123, 19, 123, 48), 'struct.pack', 'struct.pack', ({(123, 31, 123, 35): '""">H"""', (123, 37, 123, 47): 'TYPES.Long'}, {}), "('>H', TYPES.Long)", False, 'import struct\n'), ((124, 21, 124, 41), 'struct.pack', 'struct.pack', ({(124, 33, 124, 37): '""">I"""', (124, 39, 124, 40): '1'}, {}), "('>I', 1)", False, 'import struct\n'), ((131, 28, 131, 60), 'struct.pack', 'struct.pack', ({(131, 40, 131, 44): '""">L"""', (131, 46, 131, 59): 'pointer_value'}, {}), "('>L', pointer_value)", False, 'import struct\n'), ((325, 18, 325, 40), 'struct.pack', 'struct.pack', ({(325, 30, 325, 34): '""">H"""', (325, 36, 325, 39): 'key'}, {}), "('>H', key)", False, 'import struct\n'), ((327, 19, 327, 48), 'struct.pack', 'struct.pack', ({(327, 31, 327, 35): '""">H"""', (327, 37, 327, 47): 'value_type'}, {}), "('>H', value_type)", False, 'import struct\n'), ((135, 29, 135, 65), 'struct.pack', 'struct.pack', ({(135, 41, 135, 45): '""">L"""', (135, 47, 135, 64): 'thumbnail_pointer'}, {}), "('>L', thumbnail_pointer)", False, 'import struct\n'), ((196, 24, 196, 49), 'struct.pack', 'struct.pack', ({(196, 36, 196, 40): '""">I"""', (196, 42, 196, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((204, 24, 204, 49), 'struct.pack', 'struct.pack', ({(204, 36, 204, 40): '""">I"""', (204, 42, 204, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((211, 24, 211, 49), 'struct.pack', 'struct.pack', ({(211, 36, 211, 40): '""">I"""', (211, 42, 211, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((218, 24, 218, 49), 'struct.pack', 'struct.pack', ({(218, 36, 218, 40): '""">I"""', (218, 42, 218, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((230, 24, 230, 49), 'struct.pack', 'struct.pack', ({(230, 36, 230, 40): '""">I"""', (230, 42, 230, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((246, 20, 246, 45), 'struct.pack', 'struct.pack', ({(246, 32, 246, 36): '""">I"""', (246, 38, 246, 44): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((260, 20, 260, 45), 'struct.pack', 'struct.pack', ({(260, 32, 260, 36): '""">I"""', (260, 38, 260, 44): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((238, 24, 238, 46), 'struct.pack', 'struct.pack', ({(238, 36, 238, 40): '""">L"""', (238, 42, 238, 45): 'num'}, {}), "('>L', num)", False, 'import struct\n'), ((238, 49, 238, 71), 'struct.pack', 'struct.pack', ({(238, 61, 238, 65): '""">L"""', (238, 67, 238, 70): 'den'}, {}), "('>L', den)", False, 'import struct\n'), ((252, 24, 252, 46), 'struct.pack', 'struct.pack', ({(252, 36, 252, 40): '""">l"""', (252, 42, 252, 45): 'num'}, {}), "('>l', num)", False, 'import struct\n'), ((252, 49, 252, 71), 'struct.pack', 'struct.pack', ({(252, 61, 252, 65): '""">l"""', (252, 67, 252, 70): 'den'}, {}), "('>l', den)", False, 'import struct\n'), ((265, 24, 265, 49), 'struct.pack', 'struct.pack', ({(265, 36, 265, 40): '""">I"""', (265, 42, 265, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((244, 30, 244, 52), 'struct.pack', 'struct.pack', ({(244, 42, 244, 46): '""">L"""', (244, 48, 244, 51): 'num'}, {}), "('>L', num)", False, 'import struct\n'), ((245, 32, 245, 54), 'struct.pack', 'struct.pack', ({(245, 44, 245, 48): '""">L"""', (245, 50, 245, 53): 'den'}, {}), "('>L', den)", False, 'import struct\n'), ((281, 24, 281, 49), 'struct.pack', 'struct.pack', ({(281, 36, 281, 40): '""">I"""', (281, 42, 281, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((258, 30, 258, 52), 'struct.pack', 'struct.pack', ({(258, 42, 258, 46): '""">l"""', (258, 48, 258, 51): 'num'}, {}), "('>l', num)", False, 'import struct\n'), ((259, 32, 259, 54), 'struct.pack', 'struct.pack', ({(259, 44, 259, 48): '""">l"""', (259, 50, 259, 53): 'den'}, {}), "('>l', den)", False, 'import struct\n'), ((289, 24, 289, 49), 'struct.pack', 'struct.pack', ({(289, 36, 289, 40): '""">I"""', (289, 42, 289, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((296, 24, 296, 49), 'struct.pack', 'struct.pack', ({(296, 36, 296, 40): '""">I"""', (296, 42, 296, 48): 'offset'}, {}), "('>I', offset)", False, 'import struct\n'), ((300, 20, 300, 45), 'struct.pack', 'struct.pack', ({(300, 32, 300, 36): '""">I"""', (300, 38, 300, 44): 'offset'}, {}), "('>I', offset)", False, 'import struct\n')]