// Copyright 2014 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fxcodec/tiff/tiff_decoder.h"

#include <memory>
#include <utility>

#include "core/fxcodec/cfx_codec_memory.h"
#include "core/fxcodec/fx_codec.h"
#include "core/fxcodec/fx_codec_def.h"
#include "core/fxcodec/progressive_decoder_context.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_memcpy_wrappers.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/fx_stream.h"
#include "core/fxcrt/fx_system.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/span_util.h"
#include "core/fxge/dib/cfx_dibitmap.h"
#include "core/fxge/dib/fx_dib.h"

extern "C" {
#if defined(USE_SYSTEM_LIBTIFF)
#include <tiffio.h>
#else
#include "third_party/libtiff/tiffio.h"
#endif
}  // extern C

namespace {

// For use with std::unique_ptr<TIFF>.
struct TiffDeleter {
  inline void operator()(TIFF* context) { TIFFClose(context); }
};

// For use with std::unique_ptr<TIFFOpenOptions>.
struct TIFFOpenOptionsDeleter {
  inline void operator()(TIFFOpenOptions* options) {
    TIFFOpenOptionsFree(options);
  }
};

}  // namespace

class CTiffContext final : public ProgressiveDecoderContext {
 public:
  CTiffContext() = default;
  ~CTiffContext() override = default;

  bool InitDecoder(const RetainPtr<IFX_SeekableReadStream>& file_ptr);
  bool LoadFrameInfo(int32_t frame,
                     int32_t* width,
                     int32_t* height,
                     int32_t* comps,
                     int32_t* bpc,
                     CFX_DIBAttribute* pAttribute);
  bool Decode(RetainPtr<CFX_DIBitmap> bitmap);

  RetainPtr<IFX_SeekableReadStream> io_in() const { return io_in_; }
  uint32_t offset() const { return offset_; }
  void set_offset(uint32_t offset) { offset_ = offset; }

 private:
  RetainPtr<IFX_SeekableReadStream> io_in_;
  uint32_t offset_ = 0;
  std::unique_ptr<TIFF, TiffDeleter> tif_ctx_;
};

void* _TIFFcalloc(tmsize_t nmemb, tmsize_t siz) {
  return FXMEM_DefaultCalloc(nmemb, siz);
}

void* _TIFFmalloc(tmsize_t size) {
  return FXMEM_DefaultAlloc(size);
}

void _TIFFfree(void* ptr) {
  if (ptr) {
    FXMEM_DefaultFree(ptr);
  }
}

void* _TIFFrealloc(void* ptr, tmsize_t size) {
  return FXMEM_DefaultRealloc(ptr, size);
}

void _TIFFmemset(void* ptr, int val, tmsize_t size) {
  UNSAFE_TODO(FXSYS_memset(ptr, val, static_cast<size_t>(size)));
}

void _TIFFmemcpy(void* des, const void* src, tmsize_t size) {
  UNSAFE_TODO(FXSYS_memcpy(des, src, static_cast<size_t>(size)));
}

int _TIFFmemcmp(const void* ptr1, const void* ptr2, tmsize_t size) {
  return UNSAFE_TODO(memcmp(ptr1, ptr2, static_cast<size_t>(size)));
}

namespace {

tsize_t tiff_read(thandle_t context, tdata_t buf, tsize_t length) {
  CTiffContext* pTiffContext = reinterpret_cast<CTiffContext*>(context);
  FX_SAFE_UINT32 increment = pTiffContext->offset();
  increment += length;
  if (!increment.IsValid()) {
    return 0;
  }

  FX_FILESIZE offset = pTiffContext->offset();
  // SAFETY: required from caller.
  if (!pTiffContext->io_in()->ReadBlockAtOffset(
          UNSAFE_BUFFERS(pdfium::span(static_cast<uint8_t*>(buf),
                                      static_cast<size_t>(length))),
          offset)) {
    return 0;
  }
  pTiffContext->set_offset(increment.ValueOrDie());
  if (offset + length > pTiffContext->io_in()->GetSize()) {
    return pdfium::checked_cast<tsize_t>(pTiffContext->io_in()->GetSize() -
                                         offset);
  }
  return length;
}

tsize_t tiff_write(thandle_t context, tdata_t buf, tsize_t length) {
  NOTREACHED();
}

toff_t tiff_seek(thandle_t context, toff_t offset, int whence) {
  CTiffContext* pTiffContext = reinterpret_cast<CTiffContext*>(context);
  FX_SAFE_FILESIZE safe_offset = offset;
  if (!safe_offset.IsValid()) {
    return static_cast<toff_t>(-1);
  }
  FX_FILESIZE file_offset = safe_offset.ValueOrDie();

  switch (whence) {
    case 0: {
      if (file_offset > pTiffContext->io_in()->GetSize()) {
        return static_cast<toff_t>(-1);
      }
      pTiffContext->set_offset(pdfium::checked_cast<uint32_t>(file_offset));
      return pTiffContext->offset();
    }
    case 1: {
      FX_SAFE_UINT32 new_increment = pTiffContext->offset();
      new_increment += file_offset;
      if (!new_increment.IsValid()) {
        return static_cast<toff_t>(-1);
      }
      pTiffContext->set_offset(new_increment.ValueOrDie());
      return pTiffContext->offset();
    }
    case 2: {
      if (pTiffContext->io_in()->GetSize() < file_offset) {
        return static_cast<toff_t>(-1);
      }
      pTiffContext->set_offset(pdfium::checked_cast<uint32_t>(
          pTiffContext->io_in()->GetSize() - file_offset));
      return pTiffContext->offset();
    }
    default:
      return static_cast<toff_t>(-1);
  }
}

int tiff_close(thandle_t context) {
  return 0;
}

toff_t tiff_get_size(thandle_t context) {
  CTiffContext* pTiffContext = reinterpret_cast<CTiffContext*>(context);
  return static_cast<toff_t>(pTiffContext->io_in()->GetSize());
}

int tiff_map(thandle_t context, tdata_t*, toff_t*) {
  return 0;
}

void tiff_unmap(thandle_t context, tdata_t, toff_t) {}

}  // namespace

bool CTiffContext::InitDecoder(
    const RetainPtr<IFX_SeekableReadStream>& file_ptr) {
  // Limit set to make fuzzers happy. If this causes problems in the real world,
  // then adjust as needed.
  static constexpr tmsize_t kMaxTiffAllocBytes = 1536 * 1024 * 1024;  // 1.5 GB
  std::unique_ptr<TIFFOpenOptions, TIFFOpenOptionsDeleter> options(
      TIFFOpenOptionsAlloc());
  CHECK(options);
  TIFFOpenOptionsSetMaxCumulatedMemAlloc(options.get(), kMaxTiffAllocBytes);

  io_in_ = file_ptr;
  tif_ctx_.reset(TIFFClientOpenExt(
      /*name=*/"Tiff Image", /*mode=*/"r", /*clientdata=*/this, tiff_read,
      tiff_write, tiff_seek, tiff_close, tiff_get_size, tiff_map, tiff_unmap,
      options.get()));
  return !!tif_ctx_;
}

bool CTiffContext::LoadFrameInfo(int32_t frame,
                                 int32_t* width,
                                 int32_t* height,
                                 int32_t* comps,
                                 int32_t* bpc,
                                 CFX_DIBAttribute* pAttribute) {
  if (!TIFFSetDirectory(tif_ctx_.get(), (uint16_t)frame)) {
    return false;
  }

  uint32_t tif_width = 0;
  uint32_t tif_height = 0;
  uint16_t tif_comps = 0;
  uint16_t tif_bpc = 0;
  uint32_t tif_rps = 0;
  TIFFGetField(tif_ctx_.get(), TIFFTAG_IMAGEWIDTH, &tif_width);
  TIFFGetField(tif_ctx_.get(), TIFFTAG_IMAGELENGTH, &tif_height);
  TIFFGetField(tif_ctx_.get(), TIFFTAG_SAMPLESPERPIXEL, &tif_comps);
  TIFFGetField(tif_ctx_.get(), TIFFTAG_BITSPERSAMPLE, &tif_bpc);
  TIFFGetField(tif_ctx_.get(), TIFFTAG_ROWSPERSTRIP, &tif_rps);

  uint16_t tif_resunit = 0;
  if (TIFFGetField(tif_ctx_.get(), TIFFTAG_RESOLUTIONUNIT, &tif_resunit)) {
    pAttribute->dpi_unit_ =
        static_cast<CFX_DIBAttribute::ResUnit>(tif_resunit - 1);
  } else {
    pAttribute->dpi_unit_ = CFX_DIBAttribute::kResUnitInch;
  }

  float tif_xdpi = 0.0f;
  TIFFGetField(tif_ctx_.get(), TIFFTAG_XRESOLUTION, &tif_xdpi);
  if (tif_xdpi) {
    pAttribute->x_dpi_ = static_cast<int32_t>(tif_xdpi + 0.5f);
  }

  float tif_ydpi = 0.0f;
  TIFFGetField(tif_ctx_.get(), TIFFTAG_YRESOLUTION, &tif_ydpi);
  if (tif_ydpi) {
    pAttribute->y_dpi_ = static_cast<int32_t>(tif_ydpi + 0.5f);
  }

  FX_SAFE_INT32 checked_width = tif_width;
  FX_SAFE_INT32 checked_height = tif_height;
  if (!checked_width.IsValid() || !checked_height.IsValid()) {
    return false;
  }

  *width = checked_width.ValueOrDie();
  *height = checked_height.ValueOrDie();
  *comps = tif_comps;
  *bpc = tif_bpc;
  if (tif_rps > tif_height) {
    tif_rps = tif_height;
    TIFFSetField(tif_ctx_.get(), TIFFTAG_ROWSPERSTRIP, tif_rps);
  }
  return true;
}

bool CTiffContext::Decode(RetainPtr<CFX_DIBitmap> bitmap) {
  // TODO(crbug.com/355630556): Consider adding support for
  // `FXDIB_Format::kBgraPremul`
  CHECK_EQ(bitmap->GetFormat(), FXDIB_Format::kBgra);
  const uint32_t img_width = bitmap->GetWidth();
  const uint32_t img_height = bitmap->GetHeight();
  uint32_t width = 0;
  uint32_t height = 0;
  TIFFGetField(tif_ctx_.get(), TIFFTAG_IMAGEWIDTH, &width);
  TIFFGetField(tif_ctx_.get(), TIFFTAG_IMAGELENGTH, &height);
  if (img_width != width || img_height != height) {
    return false;
  }

  uint16_t rotation = ORIENTATION_TOPLEFT;
  TIFFGetField(tif_ctx_.get(), TIFFTAG_ORIENTATION, &rotation);
  uint32_t* data =
      fxcrt::reinterpret_span<uint32_t>(bitmap->GetWritableBuffer()).data();
  if (!TIFFReadRGBAImageOriented(tif_ctx_.get(), img_width, img_height, data,
                                 rotation, 1)) {
    return false;
  }

  for (uint32_t row = 0; row < img_height; row++) {
    auto row_span = bitmap->GetWritableScanlineAs<FX_BGRA_STRUCT<uint8_t>>(row);
    for (auto& pixel : row_span) {
      std::swap(pixel.blue, pixel.red);
    }
  }
  return true;
}

namespace fxcodec {

// static
std::unique_ptr<ProgressiveDecoderContext> TiffDecoder::CreateDecoder(
    const RetainPtr<IFX_SeekableReadStream>& file_ptr) {
  auto pDecoder = std::make_unique<CTiffContext>();
  if (!pDecoder->InitDecoder(file_ptr)) {
    return nullptr;
  }

  return pDecoder;
}

// static
bool TiffDecoder::LoadFrameInfo(ProgressiveDecoderContext* context,
                                int32_t frame,
                                int32_t* width,
                                int32_t* height,
                                int32_t* comps,
                                int32_t* bpc,
                                CFX_DIBAttribute* pAttribute) {
  DCHECK(pAttribute);

  auto* ctx = static_cast<CTiffContext*>(context);
  return ctx->LoadFrameInfo(frame, width, height, comps, bpc, pAttribute);
}

// static
bool TiffDecoder::Decode(ProgressiveDecoderContext* context,
                         RetainPtr<CFX_DIBitmap> bitmap) {
  auto* ctx = static_cast<CTiffContext*>(context);
  return ctx->Decode(std::move(bitmap));
}

}  // namespace fxcodec
