"""Extract EXIF from JPEG files."""

from typing import BinaryIO, Tuple

from exifread.core.exceptions import InvalidExif
from exifread.core.utils import ord_
from exifread.exif_log import get_logger

logger = get_logger()


def _increment_base(data, base) -> int:
    return ord_(data[base + 2]) * 256 + ord_(data[base + 3]) + 2


def _get_initial_base(fh: BinaryIO, data: bytes, fake_exif: int) -> Tuple[int, int]:
    base = 2
    logger.debug(
        "data[2]=0x%X data[3]=0x%X data[6:10]=%s",
        ord_(data[2]),
        ord_(data[3]),
        data[6:10],
    )
    while ord_(data[2]) == 0xFF and data[6:10] in (b"JFIF", b"JFXX", b"OLYM", b"Phot"):
        length = ord_(data[4]) * 256 + ord_(data[5])
        logger.debug(" Length offset is %s", length)
        fh.read(length - 8)
        # fake an EXIF beginning of file
        # I don't think this is used. --gd
        data = b"\xFF\x00" + fh.read(10)
        fake_exif = 1
        if base > 2:
            logger.debug(" Added to base")
            base = base + length + 4 - 2
        else:
            logger.debug(" Added to zero")
            base = length + 4
        logger.debug(" Set segment base to 0x%X", base)
    return base, fake_exif


def _get_base(base: int, data: bytes) -> int:
    # pylint: disable=too-many-statements
    while True:
        logger.debug(" Segment base 0x%X", base)
        if data[base : base + 2] == b"\xFF\xE1":
            # APP1
            logger.debug("  APP1 at base 0x%X", base)
            logger.debug(
                "  Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])
            )
            logger.debug("  Code: %s", data[base + 4 : base + 8])
            if data[base + 4 : base + 8] == b"Exif":
                logger.debug(
                    "  Decrement base by 2 to get to pre-segment header (for compatibility with later code)"
                )
                base -= 2
                break
            increment = _increment_base(data, base)
            logger.debug(" Increment base by %s", increment)
            base += increment
        elif data[base : base + 2] == b"\xFF\xE0":
            # APP0
            logger.debug("  APP0 at base 0x%X", base)
            logger.debug(
                "  Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])
            )
            logger.debug("  Code: %s", data[base + 4 : base + 8])
            increment = _increment_base(data, base)
            logger.debug(" Increment base by %s", increment)
            base += increment
        elif data[base : base + 2] == b"\xFF\xE2":
            # APP2
            logger.debug("  APP2 at base 0x%X", base)
            logger.debug(
                "  Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])
            )
            logger.debug(" Code: %s", data[base + 4 : base + 8])
            increment = _increment_base(data, base)
            logger.debug(" Increment base by %s", increment)
            base += increment
        elif data[base : base + 2] == b"\xFF\xEE":
            # APP14
            logger.debug("  APP14 Adobe segment at base 0x%X", base)
            logger.debug(
                "  Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])
            )
            logger.debug("  Code: %s", data[base + 4 : base + 8])
            increment = _increment_base(data, base)
            logger.debug(" Increment base by %s", increment)
            base += increment
            logger.debug(
                "  There is useful EXIF-like data here, but we have no parser for it."
            )
        elif data[base : base + 2] == b"\xFF\xDB":
            logger.debug(
                "  JPEG image data at base 0x%X No more segments are expected.", base
            )
            break
        elif data[base : base + 2] == b"\xFF\xD8":
            # APP12
            logger.debug("  FFD8 segment at base 0x%X", base)
            logger.debug(
                "  Got 0x%X 0x%X and %s instead",
                ord_(data[base]),
                ord_(data[base + 1]),
                data[4 + base : 10 + base],
            )
            logger.debug(
                "  Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])
            )
            logger.debug("  Code: %s", data[base + 4 : base + 8])
            increment = _increment_base(data, base)
            logger.debug("  Increment base by %s", increment)
            base += increment
        elif data[base : base + 2] == b"\xFF\xEC":
            # APP12
            logger.debug(
                "  APP12 XMP (Ducky) or Pictureinfo segment at base 0x%X", base
            )
            logger.debug(
                "  Got 0x%X and 0x%X instead", ord_(data[base]), ord_(data[base + 1])
            )
            logger.debug(
                "  Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])
            )
            logger.debug("Code: %s", data[base + 4 : base + 8])
            increment = _increment_base(data, base)
            logger.debug("  Increment base by %s", increment)
            base += increment
            logger.debug(
                (
                    "  There is useful EXIF-like data here (quality, comment, copyright), "
                    "but we have no parser for it."
                )
            )
        else:
            try:
                increment = _increment_base(data, base)
                logger.debug(
                    "  Got 0x%X and 0x%X instead",
                    ord_(data[base]),
                    ord_(data[base + 1]),
                )
            except IndexError as err:
                raise InvalidExif(
                    "Unexpected/unhandled segment type or file content."
                ) from err
            logger.debug("  Increment base by %s", increment)
            base += increment
    return base


def find_jpeg_exif(fh: BinaryIO, data: bytes, fake_exif: int) -> Tuple[int, bytes, int]:
    logger.debug(
        "JPEG format recognized data[0:2]=0x%X%X", ord_(data[0]), ord_(data[1])
    )

    base, fake_exif = _get_initial_base(fh, data, fake_exif)

    # Big ugly patch to deal with APP2 (or other) data coming before APP1
    fh.seek(0)
    # in theory, this could be insufficient since 64K is the maximum size--gd
    data = fh.read(base + 4000)

    base = _get_base(base, data)

    fh.seek(base + 12)
    if ord_(data[2 + base]) == 0xFF and data[6 + base : 10 + base] == b"Exif":
        # detected EXIF header
        offset = fh.tell()
        endian = fh.read(1)
        # HACK TEST:  endian = 'M'
    elif ord_(data[2 + base]) == 0xFF and data[6 + base : 10 + base + 1] == b"Ducky":
        # detected Ducky header.
        logger.debug(
            "EXIF-like header (normally 0xFF and code): 0x%X and %s",
            ord_(data[2 + base]),
            data[6 + base : 10 + base + 1],
        )
        offset = fh.tell()
        endian = fh.read(1)
    elif ord_(data[2 + base]) == 0xFF and data[6 + base : 10 + base + 1] == b"Adobe":
        # detected APP14 (Adobe)
        logger.debug(
            "EXIF-like header (normally 0xFF and code): 0x%X and %s",
            ord_(data[2 + base]),
            data[6 + base : 10 + base + 1],
        )
        offset = fh.tell()
        endian = fh.read(1)
    else:
        # no EXIF information
        msg = "No EXIF header expected data[2+base]==0xFF and data[6+base:10+base]===Exif (or Duck)"
        msg += " Did get 0x%X and %r" % (
            ord_(data[2 + base]),
            data[6 + base : 10 + base + 1],
        )
        raise InvalidExif(msg)
    return offset, endian, fake_exif
