/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkJpegInfo.h"

#include "SkTo.h"

#ifndef SK_HAS_JPEG_LIBRARY

namespace {
class JpegSegment {
public:
    JpegSegment(const void* data, size_t size)
        : fData(static_cast<const char*>(data))
        , fSize(size)
        , fOffset(0)
        , fLength(0) {}
    bool read() {
        if (!this->readBigendianUint16(&fMarker)) {
            return false;
        }
        if (JpegSegment::StandAloneMarker(fMarker)) {
            fLength = 0;
            fBuffer = nullptr;
            return true;
        }
        if (!this->readBigendianUint16(&fLength) || fLength < 2) {
            return false;
        }
        fLength -= 2;  // Length includes itself for some reason.
        if (fOffset + fLength > fSize) {
            return false;  // Segment too long.
        }
        fBuffer = &fData[fOffset];
        fOffset += fLength;
        return true;
    }

    bool isSOF() {
        return (fMarker & 0xFFF0) == 0xFFC0 && fMarker != 0xFFC4 &&
               fMarker != 0xFFC8 && fMarker != 0xFFCC;
    }
    uint16_t marker() { return fMarker; }
    uint16_t length() { return fLength; }
    const char* data() { return fBuffer; }

    static uint16_t GetBigendianUint16(const char* ptr) {
        // "the most significant byte shall come first"
        return (static_cast<uint8_t>(ptr[0]) << 8) |
            static_cast<uint8_t>(ptr[1]);
    }

private:
    const char* const fData;
    const size_t fSize;
    size_t fOffset;
    const char* fBuffer;
    uint16_t fMarker;
    uint16_t fLength;

    bool readBigendianUint16(uint16_t* value) {
        if (fOffset + 2 > fSize) {
            return false;
        }
        *value = JpegSegment::GetBigendianUint16(&fData[fOffset]);
        fOffset += 2;
        return true;
    }
    static bool StandAloneMarker(uint16_t marker) {
        // RST[m] markers or SOI, EOI, TEM
        return (marker & 0xFFF8) == 0xFFD0 || marker == 0xFFD8 ||
               marker == 0xFFD9 || marker == 0xFF01;
    }
};
}  // namespace

bool SkGetJpegInfo(const void* data, size_t len,
                   SkISize* size,
                   SkEncodedInfo::Color* colorType,
                   SkEncodedOrigin* orientation) {
    static const uint16_t kSOI = 0xFFD8;
    static const uint16_t kAPP0 = 0xFFE0;
    JpegSegment segment(data, len);
    if (!segment.read() || segment.marker() != kSOI) {
        return false;  // not a JPEG
    }
    if (!segment.read() || segment.marker() != kAPP0) {
        return false;  // not an APP0 segment
    }
    static const char kJfif[] = {'J', 'F', 'I', 'F', '\0'};
    SkASSERT(segment.data());
    if (SkToSizeT(segment.length()) < sizeof(kJfif) ||
        0 != memcmp(segment.data(), kJfif, sizeof(kJfif))) {
        return false;  // Not JFIF JPEG
    }
    do {
        if (!segment.read()) {
            return false;  // malformed JPEG
        }
    } while (!segment.isSOF());
    if (segment.length() < 6) {
        return false;  // SOF segment is short
    }
    if (8 != segment.data()[0]) {
        return false;  // Only support 8-bit precision
    }
    int numberOfComponents = segment.data()[5];
    if (numberOfComponents != 1 && numberOfComponents != 3) {
        return false;  // Invalid JFIF
    }
    if (size) {
        *size = {JpegSegment::GetBigendianUint16(&segment.data()[3]),
                 JpegSegment::GetBigendianUint16(&segment.data()[1])};
    }
    if (colorType) {
        *colorType = numberOfComponents == 3 ? SkEncodedInfo::kYUV_Color
                                             : SkEncodedInfo::kGray_Color;
    }
    if (orientation) {
        *orientation = kTopLeft_SkEncodedOrigin;
    }
    return true;
}
#endif  // SK_HAS_JPEG_LIBRARY
