// Copyright (C) 2020-2023 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.
// #include "rust-session-manager.h"

#ifndef RUST_SESSION_MANAGER_H
#define RUST_SESSION_MANAGER_H

#include "rust-linemap.h"
#include "rust-backend.h"
#include "rust-hir-map.h"
#include "safe-ctype.h"

#include "config.h"
#include "rust-system.h"
#include "coretypes.h"
#include "options.h"

namespace Rust {
// parser forward decl
template <typename ManagedTokenSource> class Parser;
class Lexer;
// crate forward decl
namespace AST {
struct Crate;
}
// crate forward decl
namespace HIR {
struct Crate;
}

/* Data related to target, most useful for conditional compilation and
 * whatever. */
struct TargetOptions
{
  /* TODO: maybe make private and access through helpers to allow changes to
   * impl */
  std::unordered_map<std::string, std::unordered_set<std::string> > features;

public:
  // Returns whether a key is defined in the feature set.
  bool has_key (std::string key) const
  {
    return features.find (key) != features.end ();
  }

  // Returns whether a key exists with the given value in the feature set.
  bool has_key_value_pair (std::string key, std::string value) const
  {
    auto it = features.find (key);
    if (it != features.end ())
      {
	auto set = it->second;
	auto it2 = set.find (value);
	if (it2 != set.end ())
	  return true;
      }
    return false;
  }

  /* Returns the singular value from the key, or if the key has multiple, an
   * empty string. */
  std::string get_singular_value (std::string key) const
  {
    auto it = features.find (key);
    if (it != features.end ())
      {
	auto set = it->second;
	if (set.size () == 1)
	  return *set.begin ();
      }
    return "";
  }

  /* Returns all values associated with a key (including none), or an empty
   * set if no key is found. */
  std::unordered_set<std::string> get_values_for_key (std::string key) const
  {
    auto it = features.find (key);
    if (it != features.end ())
      return it->second;
    return {};
  }

  /* Inserts a key (no value) into the feature set. This will do nothing if
   * the key already exists. This returns whether the insertion was successful
   * (i.e. whether key already existed). */
  bool insert_key (std::string key)
  {
    return features
      .insert (std::make_pair (key, std::unordered_set<std::string> ()))
      .second;
  }

  // Inserts a key-value pair into the feature set.
  void insert_key_value_pair (std::string key, std::string value)
  {
    auto existing_set = get_values_for_key (key);
    existing_set.insert (std::move (value));
    features[std::move (key)] = std::move (existing_set);
  }

  // Dump all target options to stderr.
  void dump_target_options () const;

  /* Creates derived values and implicit enables after all target info is
   * added (e.g. "unix"). */
  void init_derived_values ();

  /* Enables all requirements for the feature given, and will enable feature
   * itself if not enabled. */
  void enable_implicit_feature_reqs (std::string feature);

  /* According to reference, Rust uses either multi-map key-values or just
   * values (although values may be aliases for a key-value value). This seems
   * like overkill. Thus, depending on whether the attributes used in cfg are
   * fixed or not, I think I'll either put each non-multimap "key-value" as a
   * separate field and have the multimap "key-values" in a regular map for
   * that one key, or actually use a multimap.
   *
   * rustc itself uses a set of key-value tuples where the second tuple
   * element is optional. This gets rid of the requirement to make a
   * multi-map, I guess, but seems like it might make search slow (unless all
   * "is defined"-only ones have empty string as second element). */
  /* cfg attributes:
   * - target_arch: single value
   * - target_feature: multiple values possible
   * - target_os: single value
   * - target_family: single value (or no value?)
   * - unix: set when target_family = "unix"
   * - windows: set when target_family = "windows"
   *  - if these are just syntactic sugar, then maybe have a separate set or
   * map for this kind of stuff
   * - target_env: set when needed for disambiguation about ABI - usually
   * empty string for GNU, complicated
   *  - seems to be a single value (if any)
   * - target_endian: single value; "little" or "big"
   * - target_pointer_width: single value, "32" for 32-bit pointers, etc.
   * - target_vendor, single value
   * - test: set when testing is being done
   *  - again, seems similar to a "is defined" rather than "is equal to" like
   * unix
   * - debug_assertions: seems to "is defined"
   * - proc_macro: no idea, bad docs. seems to be boolean, so maybe "is
   * defined"
   */
};

// Defines compiler options (e.g. dump, etc.).
struct CompileOptions
{
  enum DumpOption
  {
    LEXER_DUMP,
    PARSER_AST_DUMP,
    AST_DUMP_PRETTY,
    REGISTER_PLUGINS_DUMP,
    INJECTION_DUMP,
    EXPANSION_DUMP,
    RESOLUTION_DUMP,
    TARGET_OPTION_DUMP,
    HIR_DUMP,
    HIR_DUMP_PRETTY,
    TYPE_RESOLUTION_DUMP,
  };

  std::set<DumpOption> dump_options;

  /* configuration options - actually useful for conditional compilation and
   * whatever data related to target arch, features, os, family, env, endian,
   * pointer width, vendor */
  TargetOptions target_data;
  std::string crate_name;
  bool crate_name_set_manually = false;
  bool enable_test = false;
  bool debug_assertions = false;
  bool proc_macro = false;
  std::string metadata_output_path;

  enum class Edition
  {
    E2015 = 0,
    E2018,
    E2021,
  } edition
    = Edition::E2015;

  enum class CompileStep
  {
    Ast,
    AttributeCheck,
    Expansion,
    NameResolution,
    Lowering,
    TypeCheck,
    Privacy,
    Unsafety,
    Const,
    Compilation,
    End,
  } compile_until
    = CompileStep::End;

  bool dump_option_enabled (DumpOption option) const
  {
    return dump_options.find (option) != dump_options.end ();
  }

  void enable_dump_option (DumpOption option) { dump_options.insert (option); }

  void enable_all_dump_options ()
  {
    enable_dump_option (DumpOption::LEXER_DUMP);
    enable_dump_option (DumpOption::PARSER_AST_DUMP);
    enable_dump_option (DumpOption::AST_DUMP_PRETTY);
    enable_dump_option (DumpOption::REGISTER_PLUGINS_DUMP);
    enable_dump_option (DumpOption::INJECTION_DUMP);
    enable_dump_option (DumpOption::EXPANSION_DUMP);
    enable_dump_option (DumpOption::RESOLUTION_DUMP);
    enable_dump_option (DumpOption::TARGET_OPTION_DUMP);
    enable_dump_option (DumpOption::HIR_DUMP);
    enable_dump_option (DumpOption::HIR_DUMP_PRETTY);
    enable_dump_option (DumpOption::TYPE_RESOLUTION_DUMP);
  }

  void set_crate_name (std::string name)
  {
    rust_assert (!name.empty ());

    crate_name = std::move (name);
  }

  const std::string &get_crate_name () const
  {
    rust_assert (!crate_name.empty ());
    return crate_name;
  }

  void set_edition (int raw_edition)
  {
    edition = static_cast<Edition> (raw_edition);
  }

  const Edition &get_edition () const { return edition; }

  void set_compile_step (int raw_step)
  {
    compile_until = static_cast<CompileStep> (raw_step);
  }

  const CompileStep &get_compile_until () const { return compile_until; }

  void set_metadata_output (const std::string &path)
  {
    metadata_output_path = path;
  }

  const std::string &get_metadata_output () const
  {
    return metadata_output_path;
  }

  bool metadata_output_path_set () const
  {
    return !metadata_output_path.empty ();
  }
};

/* Defines a compiler session. This is for a single compiler invocation, so
 * potentially includes parsing multiple crates. */
struct Session
{
  CompileOptions options;
  /* This should really be in a per-crate storage area but it is wiped with
   * every file so eh. */
  std::string injected_crate_name;

  /* extra files get included during late stages of compilation (e.g. macro
   * expansion) */
  std::vector<std::string> extra_files;

  // backend wrapper to GCC GENERIC
  Backend *backend;

  // backend linemap
  Linemap *linemap;

  // mappings
  Analysis::Mappings *mappings;

public:
  /* Get a reference to the static session instance */
  static Session &get_instance ();

  Session () = default;
  ~Session () = default;

  /* This initializes the compiler session. Corresponds to langhook
   * grs_langhook_init(). Note that this is called after option handling. */
  void init ();

  // delete those constructors so we don't access the singleton in any
  // other way than via `get_instance()`
  Session (Session const &) = delete;
  void operator= (Session const &) = delete;

  bool handle_option (enum opt_code code, const char *arg, HOST_WIDE_INT value,
		      int kind, location_t loc,
		      const struct cl_option_handlers *handlers);
  void handle_input_files (int num_files, const char **files);
  void init_options ();
  void handle_crate_name (const AST::Crate &parsed_crate);

  /* This function saves the filename data into the session manager using the
   * `move` semantics, and returns a C-style string referencing the input
   * std::string */
  inline const char *include_extra_file (std::string filename)
  {
    extra_files.push_back (std::move (filename));
    return extra_files.back ().c_str ();
  }

  NodeId load_extern_crate (const std::string &crate_name, Location locus);

private:
  void compile_crate (const char *filename);
  bool enable_dump (std::string arg);

  void dump_lex (Parser<Lexer> &parser) const;
  void dump_ast (Parser<Lexer> &parser, AST::Crate &crate) const;
  void dump_ast_pretty (AST::Crate &crate, bool expanded = false) const;
  void dump_ast_expanded (Parser<Lexer> &parser, AST::Crate &crate) const;
  void dump_hir (HIR::Crate &crate) const;
  void dump_hir_pretty (HIR::Crate &crate) const;
  void dump_type_resolution (HIR::Crate &crate) const;

  // pipeline stages - TODO maybe move?
  /* Register plugins pipeline stage. TODO maybe move to another object?
   * Currently dummy stage. In future will handle attribute injection
   * (top-level inner attribute creation from command line arguments), setting
   * options maybe, registering lints maybe, loading plugins maybe. */
  void register_plugins (AST::Crate &crate);

  /* Injection pipeline stage. TODO maybe move to another object? Maybe have
   * some lint checks (in future, obviously), register builtin macros, crate
   * injection. */
  void injection (AST::Crate &crate);

  /* Expansion pipeline stage. TODO maybe move to another object? Expands all
   * macros, maybe build test harness in future, AST validation, maybe create
   * macro crate (if not rustdoc).*/
  void expansion (AST::Crate &crate);

  // handle cfg_option
  bool handle_cfg_option (std::string &data);
};

} // namespace Rust

#if CHECKING_P
namespace selftest {
extern void
rust_crate_name_validation_test (void);
}
#endif // CHECKING_P

#endif