// Copyright 2022 The Centipede Authors.
//
// 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
//
//      https://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 THIRD_PARTY_CENTIPEDE_WORKDIR_MGR_H_
#define THIRD_PARTY_CENTIPEDE_WORKDIR_MGR_H_

#include <cstddef>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
#include <vector>

#include "./centipede/environment.h"
#include "./common/logging.h"

namespace fuzztest::internal {

// The Centipede work directory manager.
class WorkDir {
 public:
  // Min number of decimal digits in a shard index given `total_shards`. Used to
  // pad indices with 0's in output file names so the names are sorted by index.
  static constexpr int kDigitsInShardIndex = 6;

  // Provides APIs for getting shards of a sharded path.
  class PathShards {
   public:
    // Returns the path of the shard for `shard_index`.
    std::string Shard(size_t shard_index) const;
    // Returns the path of the shard for `my_shard_index_`.
    std::string MyShard() const;
    // Returns a glob matching all the shards.
    std::string AllShardsGlob() const;
    // Returns true if `path` looks like a shard path from this set. Matching is
    // purely lexicographical: the actual path doesn't have to exist on disk,
    // but `path` must have the exact `base_dir`/`rel_prefix` prefix,
    // including any relative "." and ".." path elements.
    bool IsShard(std::string_view path) const;
    // Returns the shard index of `path` if it is a shard parth, `nullopt`
    // otherwise.
    std::optional<size_t> GetShardIndex(std::string_view path) const;

   private:
    friend class WorkDir;

    PathShards(std::string_view base_dir, std::string_view rel_prefix,
               size_t my_shard_index);

    const std::string prefix_;
    const size_t my_shard_index_;
  };

  // Deduces the workdir properties from a provided corpus shard path and
  // coverage binary basename and hash.
  static WorkDir FromCorpusShardPath(      //
      std::string_view corpus_shard_path,  //
      std::string_view binary_name,        //
      std::string_view binary_hash);

  // Constructs an object from directly provided field values.
  WorkDir(                           //
      std::string_view workdir,      //
      std::string_view binary_name,  //
      std::string_view binary_hash,  //
      size_t my_shard_index);

  // Constructs an object by recording referenced to the field values in the
  // passed `env` object. NOTE: `env` must outlive this object.
  explicit WorkDir(const fuzztest::internal::Environment &env);

  // Not copyable and not assignable due to dual nature of the reference
  // members (that reference either the internal value holders or an external
  // `Environment`'s members).
  WorkDir(const WorkDir &) = delete;
  WorkDir &operator=(const WorkDir &) = delete;
  WorkDir(WorkDir&&) noexcept = delete;
  WorkDir& operator=(WorkDir&&) noexcept = delete;

  // Comparisons and debugging I/O (mainly for tests).
  friend bool operator==(const WorkDir &a, const WorkDir &b) {
    return a.workdir_ == b.workdir_ && a.binary_name_ == b.binary_name_ &&
           a.binary_hash_ == b.binary_hash_ &&
           a.my_shard_index_ == b.my_shard_index_;
  }
  friend bool operator!=(const WorkDir &a, const WorkDir &b) {
    return !(a == b);
  }
  friend std::ostream &operator<<(std::ostream &os, const WorkDir &wd) {
    return os << VV(wd.workdir_) << VV(wd.binary_name_) << VV(wd.binary_hash_)
              << VV(wd.my_shard_index_);
  }

  // Returns the path to a dir for dumping debug configs, command lines, logs,
  // and anything else that might aid in debugging issues or understanding the
  // provenance of the data.
  std::string DebugInfoDirPath() const;
  // Returns the path to the coverage dir.
  std::string CoverageDirPath() const;
  // Returns the path where the BinaryInfo will be serialized within workdir.
  std::string BinaryInfoDirPath() const;

  // Returns the paths for the sharded crash reproducer directory.
  PathShards CrashReproducerDirPaths() const;
  // Returns the paths for the sharded crash metadata directory, which contains
  // the crash metadata file for each crash reproducer.
  PathShards CrashMetadataDirPaths() const;
  // Returns the paths for the sharded corpus file.
  PathShards CorpusFilePaths() const;
  // Returns the paths for the sharded distilled corpus file.
  PathShards DistilledCorpusFilePaths() const;
  // Returns the paths for the sharded features file.
  PathShards FeaturesFilePaths() const;
  // Returns the paths for the sharded distilled features file.
  PathShards DistilledFeaturesFilePaths() const;

  // Returns the path for the coverage report file for my_shard_index.
  // Non-default `annotation` becomes a part of the returned filename.
  // `annotation` must not start with a '.'.
  std::string CoverageReportPath(std::string_view annotation = "") const;
  // Returns the path to the source-based coverage report directory.
  std::string SourceBasedCoverageReportPath(
      std::string_view annotation = "") const;
  // Returns the path to the coverage profile for this shard.
  std::string SourceBasedCoverageRawProfilePath() const;
  // Returns the path to the indexed code coverage file.
  std::string SourceBasedCoverageIndexedProfilePath() const;
  // Returns all shards' raw profile paths by scanning the coverage directory.
  std::vector<std::string> EnumerateRawCoverageProfiles() const;

  // Returns the path for the corpus stats report file for my_shard_index.
  // Non-default `annotation` becomes a part of the returned filename.
  // `annotation` must not start with a '.'.
  std::string CorpusStatsPath(std::string_view annotation = "") const;
  // Returns the path for the fuzzing progress stats report file for
  // `my_shard_index`.
  // Non-default `annotation` becomes a part of the returned filename.
  // `annotation` must not start with a '.'.
  std::string FuzzingStatsPath(std::string_view annotation = "") const;
  // Returns the path for the performance report file for my_shard_index.
  // Non-default `annotation` becomes a part of the returned filename.
  // `annotation` must not start with a '.'.
  std::string RUsageReportPath(std::string_view annotation = "") const;

 private:
  // Internal value holders for when the object is constructed from direct
  // values rather than an `Environment` object.
  std::string workdir_holder_;
  std::string binary_name_holder_;
  std::string binary_hash_holder_;
  size_t my_shard_index_holder_;

  // The references to either the internal `*_holder_` counterparts or an
  // externally passed `Environment` object's counterparts.
  const std::string &workdir_;
  const std::string &binary_name_;
  const std::string &binary_hash_;
  const size_t &my_shard_index_;
};

}  // namespace fuzztest::internal

#endif  // THIRD_PARTY_CENTIPEDE_WORKDIR_MGR_H_
