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

#ifndef TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
#define TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/flat_map.h"
#include "build_settings.h"
#include "gn/source_file.h"
#include "gn/target.h"

// These are internal types and helper functions for RustProjectWriter that have
// been extracted for easier testability.

// Crate Index in the generated file
using CrateIndex = size_t;

using ConfigList = std::vector<std::string>;
using Dependency = std::pair<CrateIndex, std::string>;
using DependencyList = std::vector<Dependency>;

// This class represents a crate to be serialized out as part of the
// rust-project.json file.  This is used to separate the generating
// of the data that needs to be in the file, from the file itself.
class Crate {
 public:
  Crate(SourceFile root,
        std::vector<SourceDir> extra_include_dirs,
        CrateIndex index,
        std::string label,
        std::string edition)
      : root_(root),
        extra_include_dirs_(std::move(extra_include_dirs)),
        index_(index),
        label_(label),
        edition_(edition) {}

  ~Crate() = default;

  // Add a config item to the crate.
  void AddConfigItem(std::string cfg_item) { configs_.push_back(cfg_item); }

  // Add a key-value environment variable pair used when building this crate.
  void AddRustenv(std::string key, std::string value) {
    rustenv_.emplace(key, value);
  }

  // Add another crate as a dependency of this one.
  void AddDependency(CrateIndex index, std::string name) {
    deps_.push_back(std::make_pair(index, name));
  }

  // Set the compiler arguments used to invoke the compilation of this crate
  void SetCompilerArgs(std::vector<std::string> args) { compiler_args_ = args; }

  // Set the compiler target ("e.g. x86_64-linux-kernel")
  void SetCompilerTarget(std::string target) { compiler_target_ = target; }

  // Set that this is a proc macro with the path to the output .so/dylib/dll
  void SetIsProcMacro(OutputFile proc_macro_dynamic_library) {
    proc_macro_dynamic_library_ = proc_macro_dynamic_library;
  }

  // Returns the root file for the crate.
  SourceFile& root() { return root_; }

  // Extra include dirs in addition to the directory containing the crate root.
  std::vector<SourceDir>& extra_include_dirs() { return extra_include_dirs_; }

  // Returns the crate index.
  CrateIndex index() { return index_; }

  // Returns the displayable crate label.
  const std::string& label() { return label_; }

  // Returns the Rust Edition this crate uses.
  const std::string& edition() { return edition_; }

  // Return the set of config items for this crate.
  ConfigList& configs() { return configs_; }

  // Return the set of dependencies for this crate.
  DependencyList& dependencies() { return deps_; }

  // Return the compiler arguments used to invoke the compilation of this crate
  const std::vector<std::string>& CompilerArgs() { return compiler_args_; }

  // Return the compiler target "triple" from the compiler args
  const std::optional<std::string>& CompilerTarget() {
    return compiler_target_;
  }

  // Returns whether this crate builds a proc macro .so
  const std::optional<OutputFile>& proc_macro_path() {
    return proc_macro_dynamic_library_;
  }

  // Returns environment variables applied to this, which may be necessary
  // for correct functioning of environment variables
  const base::flat_map<std::string, std::string>& rustenv() { return rustenv_; }

 private:
  SourceFile root_;
  std::vector<SourceDir> extra_include_dirs_;
  CrateIndex index_;
  std::string label_;
  std::string edition_;
  ConfigList configs_;
  DependencyList deps_;
  std::optional<std::string> compiler_target_;
  std::vector<std::string> compiler_args_;
  std::optional<OutputFile> proc_macro_dynamic_library_;
  base::flat_map<std::string, std::string> rustenv_;
};

using CrateList = std::vector<Crate>;

// Write the entire rust-project.json file contents into the given stream, based
// on the the given crates list.
void WriteCrates(const BuildSettings* build_settings,
                 CrateList& crate_list,
                 std::optional<std::string>& sysroot,
                 std::ostream& rust_project);

// Assemble the compiler arguments for the given GN Target.
std::vector<std::string> ExtractCompilerArgs(const Target* target);

// Find the value of an argument that's passed to the compiler as two
// consecutive strings in the list of arguments:  ["arg", "value"]
std::optional<std::string> FindArgValue(const char* arg,
                                        const std::vector<std::string>& args);

// Find the first argument that matches the prefix, returning the value after
// the prefix.  e.g. ˝--arg=value", is returned as "value" if the prefix
// "--arg=" is used.
std::optional<std::string> FindArgValueAfterPrefix(
    const std::string& prefix,
    const std::vector<std::string>& args);

// Find all arguments that match the given exact string, returning the value
// after.
std::vector<std::string> FindAllArgValues(const char* arg,
                                          const std::vector<std::string>& args);

// Find all arguments that match the given prefix, returning the value after
// the prefix for each one.  e.g. "--cfg=value" is returned as "value" if the
// prefix "--cfg=" is used.
std::vector<std::string> FindAllArgValuesAfterPrefix(
    const std::string& prefix,
    const std::vector<std::string>& args);

#endif  // TOOLS_GN_RUST_PROJECT_WRITER_HELPERS_H_
