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, 'copy.deepcopy', 'copy.deepcopy', False, 'import copy\n'), (303, 'struct.pack', 'struct.pack', False, 'import struct\n'), (308, 'struct.pack', 'struct.pack', False, 'import struct\n'), (99, 'struct.pack', 'struct.pack', False, 'import struct\n'), (101, 'struct.pack', 'struct.pack', False, 'import struct\n'), (102, 'struct.pack', 'struct.pack', False, 'import struct\n'), (103, 'struct.pack', 'struct.pack', False, 'import struct\n'), (109, 'struct.pack', 'struct.pack', False, 'import struct\n'), (111, 'struct.pack', 'struct.pack', False, 'import struct\n'), (112, 'struct.pack', 'struct.pack', False, 'import struct\n'), (113, 'struct.pack', 'struct.pack', False, 'import struct\n'), (120, 'struct.pack', 'struct.pack', False, 'import struct\n'), (122, 'struct.pack', 'struct.pack', False, 'import struct\n'), (123, 'struct.pack', 'struct.pack', False, 'import struct\n'), (124, 'struct.pack', 'struct.pack', False, 'import struct\n'), (131, 'struct.pack', 'struct.pack', False, 'import struct\n'), (325, 'struct.pack', 'struct.pack', False, 'import struct\n'), (327, 'struct.pack', 'struct.pack', False, 'import struct\n'), (135, 'struct.pack', 'struct.pack', False, 'import struct\n'), (196, 'struct.pack', 'struct.pack', False, 'import struct\n'), (204, 'struct.pack', 'struct.pack', False, 'import struct\n'), (211, 'struct.pack', 'struct.pack', False, 'import struct\n'), (218, 'struct.pack', 'struct.pack', False, 'import struct\n'), (230, 'struct.pack', 'struct.pack', False, 'import struct\n'), (246, 'struct.pack', 'struct.pack', False, 'import struct\n'), (260, 'struct.pack', 'struct.pack', False, 'import struct\n'), (238, 'struct.pack', 'struct.pack', False, 'import struct\n'), (238, 'struct.pack', 'struct.pack', False, 'import struct\n'), (252, 'struct.pack', 'struct.pack', False, 'import struct\n'), (252, 'struct.pack', 'struct.pack', False, 'import struct\n'), (265, 'struct.pack', 'struct.pack', False, 'import struct\n'), (244, 'struct.pack', 'struct.pack', False, 'import struct\n'), (245, 'struct.pack', 'struct.pack', False, 'import struct\n'), (281, 'struct.pack', 'struct.pack', False, 'import struct\n'), (258, 'struct.pack', 'struct.pack', False, 'import struct\n'), (259, 'struct.pack', 'struct.pack', False, 'import struct\n'), (289, 'struct.pack', 'struct.pack', False, 'import struct\n'), (296, 'struct.pack', 'struct.pack', False, 'import struct\n'), (300, 'struct.pack', 'struct.pack', False, 'import struct\n')]