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

#include "core/fxcodec/bmp/rust_bmp_context.h"

#include <utility>
#include <vector>

#include "core/fxcodec/bmp/rust_bmp_ffi.rs.h"
#include "core/fxcodec/cfx_codec_memory.h"
#include "core/fxcodec/fx_codec.h"
#include "core/fxcodec/progressive_decoder_context_delegate.h"
#include "core/fxcrt/byteorder.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/span.h"
#include "core/fxge/dib/fx_dib.h"

namespace fxcodec {

namespace {

constexpr size_t kMinHeaderBytes = 18;
constexpr size_t kHeaderSizeOffset = 14;
constexpr size_t kMaxUncompressedBytes = 1024 * 1024 * 1024;  // 1 GiB

// Malformed BMP files may specify a data offset that is before the end of the
// header. Clamp the offset to ensure it points past the header.
void ClampBmpHeaderOffset(pdfium::span<uint8_t> header, size_t total_size) {
  if (header.size() >= kMinHeaderBytes && header[0] == 'B' &&
      header[1] == 'M') {
    uint32_t bi_size =
        fxcrt::GetUInt32LSBFirst(header.subspan<kHeaderSizeOffset, 4>());
    FX_SAFE_UINT32 safe_min_off = bi_size;
    safe_min_off += kHeaderSizeOffset;
    uint32_t off_bits = fxcrt::GetUInt32LSBFirst(header.subspan<10, 4>());
    if (safe_min_off.IsValid()) {
      uint32_t min_off = safe_min_off.ValueOrDie();
      if (off_bits < min_off && min_off <= total_size) {
        fxcrt::PutUInt32LSBFirst(min_off, header.subspan<10, 4>());
      }
    }
  }
}

}  // namespace

RustBmpContext::RustBmpContext(ProgressiveDecoderContextDelegate* delegate)
    : delegate_(delegate) {}

RustBmpContext::~RustBmpContext() = default;

void RustBmpContext::SetCodecMemory(RetainPtr<CFX_CodecMemory> codec_memory) {
  codec_memory_ = std::move(codec_memory);
}

FX_FILESIZE RustBmpContext::GetAvailInput() const {
  if (!codec_memory_) {
    return 0;
  }
  return codec_memory_->GetSize();
}

void RustBmpContext::Input(RetainPtr<CFX_CodecMemory> codec_memory) {
  SetCodecMemory(std::move(codec_memory));
}

ProgressiveDecoderContext::Status RustBmpContext::DecodeImage() {
  return ContinueDecode();
}

ProgressiveDecoderContext::Status RustBmpContext::ReadHeader(
    int32_t* width,
    int32_t* height,
    int32_t* components,
    pdfium::span<const FX_ARGB>* palette,
    CFX_DIBAttribute* attribute) {
  if (header_read_) {
    *width = static_cast<int32_t>(width_);
    *height = static_cast<int32_t>(height_);
    *components = components_;
    *palette = {};
    if (attribute) {
      attribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter;
      attribute->x_dpi_ = dpi_x_;
      attribute->y_dpi_ = dpi_y_;
    }
    return ProgressiveDecoderContext::Status::kSuccess;
  }

  const auto span = codec_memory_->GetBufferSpan();
  if (span.size() < kMinHeaderBytes) {
    return ProgressiveDecoderContext::Status::kContinue;
  }

  ClampBmpHeaderOffset(codec_memory_->GetBufferSpan(), span.size());

  rust_bmp::BmpHeaderInfo header_info{};
  rust::Slice<const uint8_t> src_slice(span);
  rust_bmp::DecodeStatus status =
      rust_bmp::read_bmp_info(src_slice, header_info);
  if (status == rust_bmp::DecodeStatus::Continue) {
    return ProgressiveDecoderContext::Status::kContinue;
  }
  if (status == rust_bmp::DecodeStatus::Error) {
    return ProgressiveDecoderContext::Status::kError;
  }

  FX_SAFE_SIZE_T safe_uncompressed = header_info.width;
  safe_uncompressed *= header_info.height;
  safe_uncompressed *= header_info.components;
  if (!safe_uncompressed.IsValid() ||
      safe_uncompressed.ValueOrDie() > kMaxUncompressedBytes) {
    return ProgressiveDecoderContext::Status::kError;
  }

  width_ = header_info.width;
  height_ = header_info.height;
  components_ = header_info.components;

  if (span.size() >= 46) {
    uint32_t bi_size =
        fxcrt::GetUInt32LSBFirst(span.subspan<kHeaderSizeOffset, 4>());
    if (bi_size >= 40) {
      dpi_x_ =
          static_cast<int32_t>(fxcrt::GetUInt32LSBFirst(span.subspan<38, 4>()));
      dpi_y_ =
          static_cast<int32_t>(fxcrt::GetUInt32LSBFirst(span.subspan<42, 4>()));
    }
  }

  *width = static_cast<int32_t>(width_);
  *height = static_cast<int32_t>(height_);
  *components = components_;
  *palette = {};
  if (attribute) {
    attribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter;
    attribute->x_dpi_ = dpi_x_;
    attribute->y_dpi_ = dpi_y_;
  }
  header_read_ = true;
  return ProgressiveDecoderContext::Status::kSuccess;
}

ProgressiveDecoderContext::Status RustBmpContext::StartDecode() {
  ProgressiveDecoderContextDelegate::Format format =
      (components_ == 4) ? ProgressiveDecoderContextDelegate::Format::kArgb
                         : ProgressiveDecoderContextDelegate::Format::kRgb;
  if (!delegate_->PrepareDirectOutput(width_, height_, format)) {
    return ProgressiveDecoderContext::Status::kError;
  }

  return ContinueDecode();
}

ProgressiveDecoderContext::Status RustBmpContext::ContinueDecode() {
  pdfium::span<const uint8_t> src_span = codec_memory_->GetBufferSpan();
  rust::Slice<const uint8_t> src_slice(src_span);

  FX_SAFE_SIZE_T safe_row_bytes = width_;
  safe_row_bytes *= components_;
  safe_row_bytes += 3;
  safe_row_bytes /= 4;
  safe_row_bytes *= 4;
  if (!safe_row_bytes.IsValid()) {
    return ProgressiveDecoderContext::Status::kError;
  }
  size_t row_bytes = safe_row_bytes.ValueOrDie();

  FX_SAFE_SIZE_T buf_size = row_bytes;
  buf_size *= height_;
  if (!buf_size.IsValid()) {
    return ProgressiveDecoderContext::Status::kError;
  }

  std::vector<uint8_t> decoded_buf(buf_size.ValueOrDie());
  rust::Slice<uint8_t> out_slice(decoded_buf);
  rust_bmp::DecodeStatus status =
      rust_bmp::decode_bmp_to_buf(src_slice, out_slice, row_bytes);
  if (status == rust_bmp::DecodeStatus::Continue) {
    return ProgressiveDecoderContext::Status::kContinue;
  }
  if (status == rust_bmp::DecodeStatus::Error) {
    return ProgressiveDecoderContext::Status::kError;
  }

  pdfium::span<const uint8_t> buf_span(decoded_buf);
  for (size_t y = 0; y < height_; ++y) {
    pdfium::span<const uint8_t> row_span =
        buf_span.subspan(y * row_bytes, row_bytes);
    delegate_->ResampleScanline(static_cast<int>(y), row_span);
  }

  return ProgressiveDecoderContext::Status::kSuccess;
}

}  // namespace fxcodec
