// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause

#include "avif/internal.h"

#include <stdint.h>
#include <string.h>

avifResult avifGetExifTiffHeaderOffset(const uint8_t * exif, size_t exifSize, size_t * offset)
{
    const uint8_t tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
    const uint8_t tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
    exifSize = AVIF_MIN(exifSize, UINT32_MAX);
    for (*offset = 0; *offset + 4 < exifSize; ++*offset) {
        if (!memcmp(&exif[*offset], tiffHeaderBE, 4) || !memcmp(&exif[*offset], tiffHeaderLE, 4)) {
            return AVIF_RESULT_OK;
        }
    }
    // Couldn't find the TIFF header
    return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
}

// Returns the offset to the Exif 8-bit orientation value and AVIF_RESULT_OK, or an error.
// If the offset is set to exifSize, there was no parsing error but no orientation tag was found.
avifResult avifGetExifOrientationOffset(const uint8_t * exif, size_t exifSize, size_t * offset)
{
    const avifResult result = avifGetExifTiffHeaderOffset(exif, exifSize, offset);
    if (result != AVIF_RESULT_OK) {
        // Couldn't find the TIFF header
        return result;
    }

    avifROData raw = { exif + *offset, exifSize - *offset };
    const avifBool littleEndian = (raw.data[0] == 'I');
    avifROStream stream;
    avifROStreamStart(&stream, &raw, NULL, NULL);

    // TIFF Header
    uint32_t offsetTo0thIfd;
    if (!avifROStreamSkip(&stream, 4) || // Skip tiffHeaderBE or tiffHeaderLE.
        !avifROStreamReadU32Endianness(&stream, &offsetTo0thIfd, littleEndian)) {
        return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
    }

    avifROStreamSetOffset(&stream, offsetTo0thIfd);
    uint16_t fieldCount;
    if (!avifROStreamReadU16Endianness(&stream, &fieldCount, littleEndian)) {
        return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
    }
    for (uint16_t field = 0; field < fieldCount; ++field) { // for each field interoperability array
        uint16_t tag;
        uint16_t type;
        uint32_t count;
        uint16_t firstHalfOfValueOffset;
        if (!avifROStreamReadU16Endianness(&stream, &tag, littleEndian) || !avifROStreamReadU16Endianness(&stream, &type, littleEndian) ||
            !avifROStreamReadU32Endianness(&stream, &count, littleEndian) ||
            !avifROStreamReadU16Endianness(&stream, &firstHalfOfValueOffset, littleEndian) || !avifROStreamSkip(&stream, 2)) {
            return AVIF_RESULT_INVALID_EXIF_PAYLOAD;
        }
        // Orientation attribute according to JEITA CP-3451C section 4.6.4 (TIFF Rev. 6.0 Attribute Information):
        const uint16_t shortType = 0x03;
        if (tag == 0x0112 && type == shortType && count == 0x01) {
            // Only consider non-reserved orientation values, so that it is known that
            // the most meaningful byte of firstHalfOfValueOffset is 0.
            if (firstHalfOfValueOffset >= 1 && firstHalfOfValueOffset <= 8) {
                // Offset to the least meaningful byte of firstHalfOfValueOffset.
                *offset += avifROStreamOffset(&stream) - (littleEndian ? 4 : 3);
                return AVIF_RESULT_OK;
            }
        }
    }
    // Orientation is in the 0th IFD, so no need to parse the following ones.

    *offset = exifSize; // Signal missing orientation tag in valid Exif payload.
    return AVIF_RESULT_OK;
}

avifResult avifImageExtractExifOrientationToIrotImir(avifImage * image)
{
    const avifTransformFlags otherFlags = image->transformFlags & ~(AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR);
    size_t offset;
    const avifResult result = avifGetExifOrientationOffset(image->exif.data, image->exif.size, &offset);
    if (result != AVIF_RESULT_OK) {
        return result;
    }
    if (offset < image->exif.size) {
        const uint8_t orientation = image->exif.data[offset];
        // Mapping from Exif orientation as defined in JEITA CP-3451C section 4.6.4.A Orientation
        // to irot and imir boxes as defined in HEIF ISO/IEC 28002-12:2021 sections 6.5.10 and 6.5.12.
        switch (orientation) {
            case 1: // The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
                image->transformFlags = otherFlags;
                image->irot.angle = 0; // ignored
                image->imir.axis = 0;  // ignored
                return AVIF_RESULT_OK;
            case 2: // The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
                image->transformFlags = otherFlags | AVIF_TRANSFORM_IMIR;
                image->irot.angle = 0; // ignored
                image->imir.axis = 1;
                return AVIF_RESULT_OK;
            case 3: // The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
                image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
                image->irot.angle = 2;
                image->imir.axis = 0; // ignored
                return AVIF_RESULT_OK;
            case 4: // The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
                image->transformFlags = otherFlags | AVIF_TRANSFORM_IMIR;
                image->irot.angle = 0; // ignored
                image->imir.axis = 0;
                return AVIF_RESULT_OK;
            case 5: // The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
                image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
                image->irot.angle = 1; // applied before imir according to MIAF spec ISO/IEC 28002-12:2021 - section 7.3.6.7
                image->imir.axis = 0;
                return AVIF_RESULT_OK;
            case 6: // The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
                image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
                image->irot.angle = 3;
                image->imir.axis = 0; // ignored
                return AVIF_RESULT_OK;
            case 7: // The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
                image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT | AVIF_TRANSFORM_IMIR;
                image->irot.angle = 3; // applied before imir according to MIAF spec ISO/IEC 28002-12:2021 - section 7.3.6.7
                image->imir.axis = 0;
                return AVIF_RESULT_OK;
            case 8: // The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
                image->transformFlags = otherFlags | AVIF_TRANSFORM_IROT;
                image->irot.angle = 1;
                image->imir.axis = 0; // ignored
                return AVIF_RESULT_OK;
            default: // reserved
                break;
        }
    }

    // The orientation tag is not mandatory (only recommended) according to JEITA CP-3451C section 4.6.8.A.
    // The default value is 1 if the orientation tag is missing, meaning:
    //   The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
    image->transformFlags = otherFlags;
    image->irot.angle = 0; // ignored
    image->imir.axis = 0;  // ignored
    return AVIF_RESULT_OK;
}

#if defined(AVIF_ENABLE_EXPERIMENTAL_MINI)
uint8_t avifImageIrotImirToExifOrientation(const avifImage * image)
{
    if (!(image->transformFlags & AVIF_TRANSFORM_IROT) || image->irot.angle == 0) {
        if (!(image->transformFlags & AVIF_TRANSFORM_IMIR)) {
            return 1; // The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
        }
        if (image->imir.axis == 0) {
            return 4; // The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
        }
        // image->imir.axis == 1
        return 2; // The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
    }

    if (image->irot.angle == 1) {
        if (!(image->transformFlags & AVIF_TRANSFORM_IMIR)) {
            return 8; // The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
        }
        if (image->imir.axis == 0) {
            return 5; // The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
        }
        // image->imir.axis == 1
        return 7; // The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
    }

    if (image->irot.angle == 2) {
        if (!(image->transformFlags & AVIF_TRANSFORM_IMIR)) {
            return 3; // The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
        }
        if (image->imir.axis == 0) {
            return 2; // The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
        }
        // image->imir.axis == 1
        return 4; // The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
    }

    // image->irot.angle == 3
    if (!(image->transformFlags & AVIF_TRANSFORM_IMIR)) {
        return 6; // The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
    }
    if (image->imir.axis == 0) {
        return 7; // The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
    }
    // image->imir.axis == 1
    return 5; // The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
}
#endif // AVIF_ENABLE_EXPERIMENTAL_MINI

avifResult avifImageSetMetadataExif(avifImage * image, const uint8_t * exif, size_t exifSize)
{
    AVIF_CHECKRES(avifRWDataSet(&image->exif, exif, exifSize));
    // Ignore any Exif parsing failure.
    // TODO(wtc): Decide whether to ignore or return Exif parsing failures.
    (void)avifImageExtractExifOrientationToIrotImir(image);
    return AVIF_RESULT_OK;
}
