/*
 * Copyright 2026 Bloomberg LP
 *
 * 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 INCLUDED_BUILDBOXCOMMON_FASTCDCUTILS
#define INCLUDED_BUILDBOXCOMMON_FASTCDCUTILS

#include <buildboxcommon_protos.h>

#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>

namespace buildboxcommon {

/**
 * FastCDC 2020 content-defined chunker.
 *
 * Splits a blob into content-defined chunks and returns the digest of each
 * chunk, in order. This is a port of the canonical FastCDC algorithm as
 * described in the paper (https://ieeexplore.ieee.org/document/9055082) by
 * Wen Xia, et al., in 2020, using the same Gear table and masks as the
 * reference implementations.
 *
 * Chunking is deterministic: the same input always produces the same chunks
 * for a given configuration.
 */
class FastCDCUtils {
  public:
    /**
     * Chunking parameters. `minChunkSize` and `maxChunkSize` are derived from
     * `avgChunkSize` (avg/4 and avg*4 respectively), matching the reference
     * configuration.
     */
    struct Config {
        static constexpr size_t DEFAULT_AVG_CHUNK_SIZE =
            static_cast<size_t>(512) * 1024; // 512 KiB
        static constexpr int DEFAULT_NORMALIZATION_LEVEL = 2;
        static constexpr uint32_t DEFAULT_SEED = 0;

        // Target average chunk size in bytes. Must be a power of two.
        size_t avgChunkSize = DEFAULT_AVG_CHUNK_SIZE;
        // Normalized chunking level (0..3); higher values concentrate chunk
        // sizes more tightly around `avgChunkSize`.
        int normalizationLevel = DEFAULT_NORMALIZATION_LEVEL;
        // Seed mixed into the rolling hash, allowing distinct chunkings of the
        // same content.
        uint32_t seed = DEFAULT_SEED;
    };

    /**
     * Construct a chunker for the given configuration, precomputing the masks
     * and derived sizes.
     *
     * Throws std::invalid_argument if `avgChunkSize` is not a positive power
     * of two, if `normalizationLevel` is outside 0..3, or if the resulting
     * normalization is too extreme for `avgChunkSize`.
     */
    explicit FastCDCUtils(const Config &config);

    /**
     * Chunk the blob read from `fd` and return the digest of each chunk, in
     * order. Reads sequentially from the descriptor's current offset (never
     * seeks), so it works on regular files and pipes alike, and does not take
     * ownership of `fd` (the caller keeps it open / closes it).
     *
     * `DigestGenerator::init()` must have been called before this is used (the
     * repo-wide precondition for computing digests). Throws std::system_error
     * on a read error.
     */
    std::vector<Digest> chunkToDigests(int fd) const;

    /**
     * Convenience overload: open the file at `path` (O_RDONLY) and chunk it.
     * The file is streamed, so it is never held in memory in its entirety.
     * Throws std::system_error if the file cannot be opened.
     */
    std::vector<Digest> chunkToDigests(const std::string &path) const;

    /**
     * Convenience overload: open the file at `path` relative to the directory
     * `dirFd` (openat() semantics, O_RDONLY) and chunk it. Throws
     * std::system_error if the file cannot be opened.
     */
    std::vector<Digest> chunkToDigests(int dirFd,
                                       const std::string &path) const;

  private:
    size_t d_minSize;
    size_t d_avgSize;
    size_t d_maxSize;
    uint64_t d_maskS;
    uint64_t d_maskL;
    uint64_t d_maskSLs;
    uint64_t d_maskLLs;
    uint64_t d_seed;
    uint64_t d_shiftedSeed;

    // Return the length of the next chunk at the start of `data`.
    size_t cut(std::string_view data) const;
};

} // namespace buildboxcommon

#endif
