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

#ifndef CORE_FXCRT_ZIP_H_
#define CORE_FXCRT_ZIP_H_

#include <stdint.h>

#include <tuple>
#include <utility>

#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/span.h"

namespace fxcrt {

// Vastly simplified implementation of ideas from C++23 zip_view<>. Allows
// safe traversal of two or three ranges with a single bounds check per
// iteration.
//
// Example two range usage:
//   struct RGB { uint8_t r; uint8_t g; uint8_t b; };
//   const uint8_t gray[256] = { ... };
//   RGB rgbs[260];
//   for (auto [in, out] : Zip(gray, rgbs)) {
//     out.r = in;
//     out.g = in;
//     out.b = in;
//   }
// which fills the first 256 elements of rgbs with the corresponding gray
// value in each component, say.
//
// Differences include:
// - Only zips together two or three views instead of N.
// - Size is determined by the first view, which must be smaller than the
//   other view(s).
// - Views are passed as forwarding-references, so non-const views may be
//   assigned-to in the body of the loop.
// - Only those methods required to support use in a range-based for-loop
//   are provided.

template <typename T, typename U>
class ZipView2 {
 public:
  struct Iter {
    friend inline bool operator==(const Iter& lhs, const Iter& rhs) {
      return lhs.first == rhs.first;
    }

    // PRECONDITIONS: Iter must not have reached end().
    UNSAFE_BUFFER_USAGE Iter& operator++() {
      // SAFETY: required from caller, enforced by UNSAFE_BUFFER_USAGE.
      UNSAFE_BUFFERS(++first);
      UNSAFE_BUFFERS(++second);
      return *this;
    }

    std::pair<typename T::reference, typename U::reference> operator*() const {
      return {*first, *second};
    }

    typename T::iterator first;
    typename U::iterator second;
  };

  ZipView2(T first, U second) : first_(first), second_(second) {
    CHECK_LE(first.size(), second.size());
  }

  Iter begin() { return {first_.begin(), second_.begin()}; }
  Iter end() { return {first_.end(), second_.end()}; }

 private:
  T first_;
  U second_;
};

// Same as `ZipView2`, but with 2 inputs and 1 output.
template <typename T, typename U, typename V>
class ZipView3 {
 public:
  struct Iter {
    inline friend bool operator==(const Iter& lhs, const Iter& rhs) {
      return lhs.first == rhs.first;
    }

    // PRECONDITIONS: Iter must not have reached end().
    UNSAFE_BUFFER_USAGE Iter& operator++() {
      // SAFETY: required from caller, enforced by UNSAFE_BUFFER_USAGE.
      UNSAFE_BUFFERS(++first);
      UNSAFE_BUFFERS(++second);
      UNSAFE_BUFFERS(++third);
      return *this;
    }

    std::tuple<typename T::reference,
               typename U::reference,
               typename V::reference>
    operator*() const {
      return {*first, *second, *third};
    }

    typename T::iterator first;
    typename U::iterator second;
    typename V::iterator third;
  };

  ZipView3(T first, U second, V third)
      : first_(first), second_(second), third_(third) {
    CHECK_LE(first.size(), second.size());
    CHECK_LE(first.size(), third.size());
  }

  Iter begin() { return {first_.begin(), second_.begin(), third_.begin()}; }
  Iter end() { return {first_.end(), second_.end(), third_.end()}; }

 private:
  T first_;
  U second_;
  V third_;
};

template <typename T, typename U>
auto Zip(T&& first, U&& second) {
  return ZipView2(pdfium::span(first), pdfium::span(second));
}

template <typename T, typename U, typename V>
auto Zip(T&& first, U&& second, V&& third) {
  return ZipView3(pdfium::span(first), pdfium::span(second),
                  pdfium::span(third));
}

}  // namespace fxcrt

#endif  // CORE_FXCRT_ZIP_H_
