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

#include "components/qr_code_generator/qr_code_generator.h"

#include <algorithm>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/span_rust.h"
#include "base/numerics/safe_conversions.h"
#include "components/qr_code_generator/cpp_api_from_rust_buildflags.h"

#if BUILDFLAG(ENABLE_CPP_API_FROM_RUST)
#include "third_party/crubit/support/rs_std/iterator_adapter.h"
#include "third_party/crubit/support/rs_std/slice_ref.h"
#include "third_party/rust/qr_code/v2/qr_code.h"
#else
#include "components/qr_code_generator/qr_code_generator_ffi_glue.rs.h"
#endif

namespace qr_code_generator {

GeneratedCode::GeneratedCode() = default;
GeneratedCode::~GeneratedCode() = default;
GeneratedCode::GeneratedCode(GeneratedCode&&) = default;
GeneratedCode& GeneratedCode::operator=(GeneratedCode&&) = default;

#if BUILDFLAG(ENABLE_CPP_API_FROM_RUST)
Error RustErrorToCppError(const ::qr_code::types::QrError& rust_error) {
  if (rust_error == ::qr_code::types::QrError::MakeDataTooLong()) {
    return Error::kInputTooLong;
  }
  return Error::kUnknownError;
}

base::expected<GeneratedCode, Error> GenerateCode(
    base::span<const uint8_t> in,
    std::optional<int> min_version) {
  if (min_version.has_value() && (*min_version < 1 || 40 < *min_version)) {
    return base::unexpected(Error::kUnknownError);
  }

  rs_std::SliceRef<const uint8_t> rs_in(in);
  auto result = ::qr_code::QrCode::new_(rs_in);
  if (!result.has_value()) {
    return base::unexpected(RustErrorToCppError(result.err()));
  }
  ::qr_code::QrCode rs_code = std::move(result).value();

  if (min_version.has_value()) {
    auto rs_min_version = ::qr_code::Version::MakeNormal(*min_version);
    if (rs_code.version().width() < rs_min_version.width()) {
      result = ::qr_code::QrCode::with_version(rs_in, std::move(rs_min_version),
                                               ::qr_code::EcLevel::MakeM());
      if (!result.has_value()) {
        return base::unexpected(RustErrorToCppError(result.err()));
      }
      rs_code = std::move(result).value();
    }
  }

  GeneratedCode code;
  code.qr_size = base::checked_cast<int>(rs_code.width());
  std::ranges::transform(rs::IteratorAdapter(rs_code.iter()), rs::IteratorEnd(),
                         std::back_inserter(code.data),
                         [](bool b) -> uint8_t { return b ? 1 : 0; });
  CHECK_EQ(code.data.size(), static_cast<size_t>(code.qr_size * code.qr_size));
  return code;
}
#else
base::expected<GeneratedCode, Error> GenerateCode(
    base::span<const uint8_t> in,
    std::optional<int> min_version) {
  rust::Slice<const uint8_t> rs_in = base::SpanToRustSlice(in);

  // `min_version` might come from a fuzzer and therefore we use a lenient
  // `saturated_cast` instead of a `checked_cast`.
  int16_t rs_min_version =
      base::saturated_cast<int16_t>(min_version.value_or(0));

  std::vector<uint8_t> result_pixels;
  size_t result_width = 0;
  Error result_error = Error::kUnknownError;
  bool result_is_success = generate_qr_code_using_rust(
      rs_in, rs_min_version, result_pixels, result_width, result_error);

  if (!result_is_success) {
    return base::unexpected(result_error);
  }
  GeneratedCode code;
  code.data = std::move(result_pixels);
  code.qr_size = base::checked_cast<int>(result_width);
  CHECK_EQ(code.data.size(), static_cast<size_t>(code.qr_size * code.qr_size));
  return code;
}
#endif

}  // namespace qr_code_generator
