// Copyright 2015 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#ifndef PIEX_TIFF_PARSER_H_
#define PIEX_TIFF_PARSER_H_

#include <cstdint>
#include <memory>
#include <set>
#include <vector>

#include "src/piex_types.h"
#include "src/tiff_directory/tiff_directory.h"

namespace piex {

// Specifies the maximum number of pixels for thumbnails in each direction.
const int kThumbnailMaxDimension = 512;

// Specifies all tags that might be of interest to get the preview data.
enum GpsTags {
  kGpsTagLatitudeRef = 1,
  kGpsTagLatitude = 2,
  kGpsTagLongitudeRef = 3,
  kGpsTagLongitude = 4,
  kGpsTagAltitudeRef = 5,
  kGpsTagAltitude = 6,
  kGpsTagTimeStamp = 7,
  kGpsTagDateStamp = 29,
};

enum TiffTags {
  kExifTagColorSpace = 0xA001,
  kExifTagDateTimeOriginal = 0x9003,
  kExifTagDefaultCropSize = 0xC620,
  kExifTagExposureTime = 0x829a,
  kExifTagFnumber = 0x829d,
  kExifTagFocalLength = 0x920A,
  kExifTagGps = 0x8825,
  kExifTagHeight = 0xA003,
  kExifTagIsoSpeed = 0x8827,
  kExifTagMakernotes = 0x927C,
  kExifTagWidth = 0xA002,
  kOlymTagAspectFrame = 0x1113,
  kOlymTagCameraSettings = 0x2020,
  kOlymTagRawProcessing = 0x2040,
  kPanaTagBottomBorder = 0x006,
  kPanaTagIso = 0x0017,
  kPanaTagJpegImage = 0x002E,
  kPanaTagLeftBorder = 0x0005,
  kPanaTagRightBorder = 0x007,
  kPanaTagTopBorder = 0x0004,
  kPentaxTagColorSpace = 0x0037,
  kTiffTagArtist = 0x013B,
  kTiffTagBitsPerSample = 0x0102,
  kTiffTagCfaPatternDim = 0x828D,
  kTiffTagCompression = 0x0103,
  kTiffTagDateTime = 0x0132,
  kTiffTagExifIfd = 0x8769,
  kTiffTagImageDescription = 0x010E,
  kTiffTagImageLength = 0x0101,
  kTiffTagImageWidth = 0x0100,
  kTiffTagJpegByteCount = 0x0202,
  kTiffTagJpegOffset = 0x0201,
  kTiffTagMake = 0x010F,
  kTiffTagModel = 0x0110,
  kTiffTagOrientation = 0x0112,
  kTiffTagPhotometric = 0x0106,
  kTiffTagPlanarConfig = 0x011C,
  kTiffTagResolutionUnit = 0x0128,
  kTiffTagRowsPerStrip = 0x0116,
  kTiffTagSamplesPerPixel = 0x0115,
  kTiffTagSoftware = 0x0131,
  kTiffTagStripByteCounts = 0x0117,
  kTiffTagStripOffsets = 0x0111,
  kTiffTagSubFileType = 0x00FE,
  kTiffTagSubIfd = 0x014A,
  kTiffTagTileByteCounts = 0x0145,
  kTiffTagTileLength = 0x0143,
  kTiffTagTileOffsets = 0x0144,
  kTiffTagTileWidth = 0x0142,
  kTiffTagXresolution = 0x011A,
  kTiffTagYresolution = 0x011B,
};

typedef std::set<tiff_directory::TiffDirectory::Tag> TagSet;
typedef std::vector<tiff_directory::TiffDirectory> IfdVector;

struct TiffContent {
  IfdVector tiff_directory;
  std::unique_ptr<tiff_directory::TiffDirectory> exif_directory;
  std::unique_ptr<tiff_directory::TiffDirectory> gps_directory;
};

// Reads 2 bytes, an unsigned 16bit from 'stream' at a certain 'offset'. The
// bytes get swapped according to the desired endianness returning true on
// success. Returns false when something is wrong.
bool Get16u(StreamInterface* stream, const std::uint32_t offset,
            const tiff_directory::Endian& endian, std::uint16_t* value);

// Reads 4 bytes, an unsigned 32bit 'value' from 'stream' at a certain 'offset'.
// The bytes get swapped according to the desired endianness returning true on
// success. Returns false when something is wrong.
bool Get32u(StreamInterface* stream, const std::uint32_t offset,
            const tiff_directory::Endian& endian, std::uint32_t* value);

// Retrieves a byte vector of size 'length' from 'stream' beginning at some
// 'offset' reading the data in chunks of one MiB.
// If 'error' is not set to kOk the returned value is invalid.
std::vector<std::uint8_t> GetData(const size_t offset, const size_t length,
                                  StreamInterface* stream, Error* error);

// Retrieves the endianness of TIFF compliant data at 'tiff_offset' from
// 'stream' returning true on success. Returns false when something is wrong.
bool GetEndianness(const std::uint32_t tiff_offset, StreamInterface* stream,
                   tiff_directory::Endian* endian);

// Retrieves an image from tiff_directory. Return false when something is wrong.
bool GetImageData(const tiff_directory::TiffDirectory& tiff_directory,
                  StreamInterface* stream, Image* image);

// Retrieves the width and height from the jpeg image returning true on
// success. Returns false when something is wrong.
bool GetJpegDimensions(const std::uint32_t jpeg_offset, StreamInterface* stream,
                       std::uint16_t* width, std::uint16_t* height);

// According to Tiff/EP a thumbnail has max 256 pixels per dimension.
// http://standardsproposals.bsigroup.com/Home/getPDF/567
bool IsThumbnail(const Image& image,
                 const int max_dimension = kThumbnailMaxDimension);

// Parses through a Tiff IFD and writes all 'desired_tags' to a
// 'tiff_directory'.
// Returns false if something with the Tiff data is wrong.
bool ParseDirectory(const std::uint32_t tiff_offset,
                    const std::uint32_t ifd_offset,
                    const tiff_directory::Endian endian,
                    const TagSet& desired_tags, StreamInterface* stream,
                    tiff_directory::TiffDirectory* tiff_directory,
                    std::uint32_t* next_ifd_offset);

// Returns true if Exif orientation for the image can be obtained. False
// otherwise.
bool GetExifOrientation(StreamInterface* stream, const std::uint32_t offset,
                        std::uint32_t* orientation);

// Reads the width and height of the full resolution image. The tag groups are
// exclusive.
bool GetFullDimension32(const tiff_directory::TiffDirectory& tiff_directory,
                        std::uint32_t* width, std::uint32_t* height);

// Reads the width and height of the crop information if available.
// Returns false if an error occurred.
bool GetFullCropDimension(const tiff_directory::TiffDirectory& tiff_directory,
                          std::uint32_t* width, std::uint32_t* height);

// Enables us to parse through data that complies to the Tiff/EP specification.
class TiffParser {
 public:
  // The caller owns 'stream' and is responsible to keep it alive while the
  // TiffParser object is used.
  explicit TiffParser(StreamInterface* stream);
  TiffParser(StreamInterface* stream, const std::uint32_t offset);

  // Runs over the Tiff IFD, Exif IFD and subIFDs to get the preview image data.
  // Returns false if something with the Tiff tags is wrong.
  bool GetPreviewImageData(const TiffContent& tiff_content,
                           PreviewImageData* image_metadata);

  // Returns false if called more that once or something with the Tiff data is
  // wrong.
  bool Parse(const TagSet& desired_tags, const std::uint16_t max_number_ifds,
             TiffContent* tiff_content);

 private:
  // Disallow copy and assignment.
  TiffParser(const TiffParser&) = delete;
  TiffParser& operator=(const TiffParser&) = delete;

  bool ParseIfd(const std::uint32_t ifd_offset, const TagSet& desired_tags,
                const std::uint16_t max_number_ifds, IfdVector* tiff_directory);
  bool ParseGpsData(const tiff_directory::TiffDirectory* tiff_ifd,
                    TiffContent* tiff_content);

  StreamInterface* stream_ = nullptr;
  std::uint32_t tiff_offset_ = 0;
  tiff_directory::Endian endian_;
};

}  // namespace piex

#endif  // PIEX_TIFF_PARSER_H_