//===-- include/flang/Semantics/scope.h -------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_SEMANTICS_SCOPE_H_
#define FORTRAN_SEMANTICS_SCOPE_H_

#include "attr.h"
#include "symbol.h"
#include "flang/Common/Fortran.h"
#include "flang/Common/idioms.h"
#include "flang/Common/reference.h"
#include "flang/Parser/message.h"
#include "flang/Parser/provenance.h"
#include <list>
#include <map>
#include <optional>
#include <set>
#include <string>

namespace llvm {
class raw_ostream;
}

namespace Fortran::semantics {

using namespace parser::literals;

using common::ConstantSubscript;

class SemanticsContext;

// An equivalence object is represented by a symbol for the variable name,
// the indices for an array element, and the lower bound for a substring.
struct EquivalenceObject {
  EquivalenceObject(Symbol &symbol, std::vector<ConstantSubscript> subscripts,
      std::optional<ConstantSubscript> substringStart, parser::CharBlock source)
      : symbol{symbol}, subscripts{subscripts},
        substringStart{substringStart}, source{source} {}
  explicit EquivalenceObject(Symbol &symbol)
      : symbol{symbol}, source{symbol.name()} {}

  bool operator==(const EquivalenceObject &) const;
  bool operator<(const EquivalenceObject &) const;
  std::string AsFortran() const;

  Symbol &symbol;
  std::vector<ConstantSubscript> subscripts; // for array elem
  std::optional<ConstantSubscript> substringStart;
  parser::CharBlock source;
};
using EquivalenceSet = std::vector<EquivalenceObject>;

class Scope {
  using mapType = std::map<SourceName, MutableSymbolRef>;

public:
  ENUM_CLASS(Kind, Global, IntrinsicModules, Module, MainProgram, Subprogram,
      BlockData, DerivedType, BlockConstruct, Forall, OtherConstruct,
      ImpliedDos)
  using ImportKind = common::ImportKind;

  // Create the Global scope -- the root of the scope tree
  explicit Scope(SemanticsContext &context)
      : Scope{*this, Kind::Global, nullptr, context} {}
  Scope(Scope &parent, Kind kind, Symbol *symbol, SemanticsContext &context)
      : parent_{&parent}, kind_{kind}, symbol_{symbol}, context_{context} {
    if (symbol) {
      symbol->set_scope(this);
    }
  }
  Scope(const Scope &) = delete;

  bool operator==(const Scope &that) const { return this == &that; }
  bool operator!=(const Scope &that) const { return this != &that; }

  Scope &parent() {
    CHECK(parent_ != this);
    return *parent_;
  }
  const Scope &parent() const {
    CHECK(parent_ != this);
    return *parent_;
  }
  Kind kind() const { return kind_; }
  bool IsGlobal() const { return kind_ == Kind::Global; }
  bool IsIntrinsicModules() const { return kind_ == Kind::IntrinsicModules; }
  bool IsTopLevel() const {
    return kind_ == Kind::Global || kind_ == Kind::IntrinsicModules;
  }
  bool IsModule() const {
    return kind_ == Kind::Module &&
        !symbol_->get<ModuleDetails>().isSubmodule();
  }
  bool IsSubmodule() const {
    return kind_ == Kind::Module && symbol_->get<ModuleDetails>().isSubmodule();
  }
  bool IsDerivedType() const { return kind_ == Kind::DerivedType; }
  bool IsStmtFunction() const;
  bool IsParameterizedDerivedType() const;
  bool IsParameterizedDerivedTypeInstantiation() const {
    return kind_ == Kind::DerivedType && !symbol_;
  }
  /// Does this derived type have at least one kind parameter ?
  bool IsDerivedTypeWithKindParameter() const;
  /// Does this derived type have at least one length parameter ?
  bool IsDerivedTypeWithLengthParameter() const;
  Symbol *symbol() { return symbol_; }
  const Symbol *symbol() const { return symbol_; }
  SemanticsContext &context() const { return context_; }

  inline const Symbol *GetSymbol() const;
  const Scope *GetDerivedTypeParent() const;
  const Scope &GetDerivedTypeBase() const;
  inline std::optional<SourceName> GetName() const;
  // Returns true if this scope contains, or is, another scope.
  bool Contains(const Scope &) const;
  /// Make a scope nested in this one
  Scope &MakeScope(Kind kind, Symbol *symbol = nullptr);

  SemanticsContext &GetMutableSemanticsContext() const {
    return const_cast<SemanticsContext &>(context());
  }

  using size_type = mapType::size_type;
  using iterator = mapType::iterator;
  using const_iterator = mapType::const_iterator;

  iterator begin() { return symbols_.begin(); }
  iterator end() { return symbols_.end(); }
  const_iterator begin() const { return symbols_.begin(); }
  const_iterator end() const { return symbols_.end(); }
  const_iterator cbegin() const { return symbols_.cbegin(); }
  const_iterator cend() const { return symbols_.cend(); }

  // Return symbols in declaration order (the iterators above are in name order)
  SymbolVector GetSymbols() const;
  MutableSymbolVector GetSymbols();

  iterator find(const SourceName &name);
  const_iterator find(const SourceName &name) const {
    return symbols_.find(name);
  }
  size_type erase(const SourceName &);
  bool empty() const { return symbols_.empty(); }

  // Look for symbol by name in this scope and host (depending on imports).
  Symbol *FindSymbol(const SourceName &) const;

  // Look for component symbol by name in a derived type's scope and
  // parents'.
  Symbol *FindComponent(SourceName) const;

  /// Make a Symbol with unknown details.
  std::pair<iterator, bool> try_emplace(
      const SourceName &name, Attrs attrs = Attrs()) {
    return try_emplace(name, attrs, UnknownDetails());
  }
  /// Make a Symbol with provided details.
  template <typename D>
  common::IfNoLvalue<std::pair<iterator, bool>, D> try_emplace(
      const SourceName &name, D &&details) {
    return try_emplace(name, Attrs(), std::move(details));
  }
  /// Make a Symbol with attrs and details
  template <typename D>
  common::IfNoLvalue<std::pair<iterator, bool>, D> try_emplace(
      const SourceName &name, Attrs attrs, D &&details) {
    Symbol &symbol{MakeSymbol(name, attrs, std::move(details))};
    return symbols_.emplace(name, symbol);
  }
  // Make a copy of a symbol in this scope; nullptr if one is already there
  Symbol *CopySymbol(const Symbol &);

  std::list<EquivalenceSet> &equivalenceSets() { return equivalenceSets_; }
  const std::list<EquivalenceSet> &equivalenceSets() const {
    return equivalenceSets_;
  }
  void add_equivalenceSet(EquivalenceSet &&);
  // Cray pointers are saved as map of pointee name -> pointer symbol
  const mapType &crayPointers() const { return crayPointers_; }
  void add_crayPointer(const SourceName &, Symbol &);
  mapType &commonBlocks() { return commonBlocks_; }
  const mapType &commonBlocks() const { return commonBlocks_; }
  Symbol &MakeCommonBlock(const SourceName &);
  Symbol *FindCommonBlock(const SourceName &) const;

  /// Make a Symbol but don't add it to the scope.
  template <typename D>
  common::IfNoLvalue<Symbol &, D> MakeSymbol(
      const SourceName &name, Attrs attrs, D &&details) {
    return allSymbols.Make(*this, name, attrs, std::move(details));
  }

  std::list<Scope> &children() { return children_; }
  const std::list<Scope> &children() const { return children_; }

  // For Module scope, maintain a mapping of all submodule scopes with this
  // module as its ancestor module. AddSubmodule returns false if already there.
  Scope *FindSubmodule(const SourceName &) const;
  bool AddSubmodule(const SourceName &, Scope &);

  const DeclTypeSpec *FindType(const DeclTypeSpec &) const;
  const DeclTypeSpec &MakeNumericType(TypeCategory, KindExpr &&kind);
  const DeclTypeSpec &MakeLogicalType(KindExpr &&kind);
  const DeclTypeSpec &MakeCharacterType(
      ParamValue &&length, KindExpr &&kind = KindExpr{0});
  DeclTypeSpec &MakeDerivedType(DeclTypeSpec::Category, DerivedTypeSpec &&);
  const DeclTypeSpec &MakeTypeStarType();
  const DeclTypeSpec &MakeClassStarType();
  const DeclTypeSpec *GetType(const SomeExpr &);

  std::size_t size() const { return size_; }
  void set_size(std::size_t size) { size_ = size; }
  std::optional<std::size_t> alignment() const { return alignment_; }

  void SetAlignment(std::size_t n) {
    alignment_ = std::max(alignment_.value_or(0), n);
  }

  ImportKind GetImportKind() const;
  // Names appearing in IMPORT statements in this scope
  std::set<SourceName> importNames() const { return importNames_; }

  // Set the kind of imports from host into this scope.
  // Return an error message for incompatible kinds.
  std::optional<parser::MessageFixedText> SetImportKind(ImportKind);

  void add_importName(const SourceName &);

  // These members pertain to instantiations of parameterized derived types.
  const DerivedTypeSpec *derivedTypeSpec() const { return derivedTypeSpec_; }
  DerivedTypeSpec *derivedTypeSpec() { return derivedTypeSpec_; }
  void set_derivedTypeSpec(DerivedTypeSpec &spec) { derivedTypeSpec_ = &spec; }
  parser::Message::Reference instantiationContext() const {
    return instantiationContext_;
  };
  void set_instantiationContext(parser::Message::Reference &&mref) {
    instantiationContext_ = std::move(mref);
  }

  bool hasSAVE() const { return hasSAVE_; }
  void set_hasSAVE(bool yes = true) { hasSAVE_ = yes; }

  // The range of the source of this and nested scopes.
  const parser::CharBlock &sourceRange() const { return sourceRange_; }
  void AddSourceRange(parser::CharBlock);
  // Find the smallest scope under this one that contains source
  const Scope *FindScope(parser::CharBlock) const;
  Scope *FindScope(parser::CharBlock);

  // Attempts to find a match for a derived type instance
  const DeclTypeSpec *FindInstantiatedDerivedType(const DerivedTypeSpec &,
      DeclTypeSpec::Category = DeclTypeSpec::TypeDerived) const;

  bool IsModuleFile() const {
    return kind_ == Kind::Module && symbol_ &&
        symbol_->test(Symbol::Flag::ModFile);
  }

  void InstantiateDerivedTypes();

  const Symbol *runtimeDerivedTypeDescription() const {
    return runtimeDerivedTypeDescription_;
  }
  void set_runtimeDerivedTypeDescription(const Symbol &symbol) {
    runtimeDerivedTypeDescription_ = &symbol;
  }

private:
  Scope *parent_{
      nullptr}; // this is enclosing scope, not extended derived type base
  const Kind kind_;
  std::size_t size_{0}; // size in bytes
  std::optional<std::size_t> alignment_; // required alignment in bytes
  parser::CharBlock sourceRange_;
  const parser::CookedSource *cookedSource_{nullptr};
  Symbol *const symbol_; // if not null, symbol_->scope() == this
  std::list<Scope> children_;
  mapType symbols_;
  mapType commonBlocks_;
  std::list<EquivalenceSet> equivalenceSets_;
  mapType crayPointers_;
  std::map<SourceName, common::Reference<Scope>> submodules_;
  std::list<DeclTypeSpec> declTypeSpecs_;
  std::optional<ImportKind> importKind_;
  std::set<SourceName> importNames_;
  DerivedTypeSpec *derivedTypeSpec_{nullptr}; // dTS->scope() == this
  parser::Message::Reference instantiationContext_;
  bool hasSAVE_{false}; // scope has a bare SAVE statement
  const Symbol *runtimeDerivedTypeDescription_{nullptr};
  SemanticsContext &context_;
  // When additional data members are added to Scope, remember to
  // copy them, if appropriate, in FindOrInstantiateDerivedType().

  // Storage for all Symbols. Every Symbol is in allSymbols and every Symbol*
  // or Symbol& points to one in there.
  static Symbols<1024> allSymbols;

  bool CanImport(const SourceName &) const;
  const DeclTypeSpec &MakeLengthlessType(DeclTypeSpec &&);

  friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Scope &);
};

// Inline so that it can be called from Evaluate without a link-time dependency.

inline const Symbol *Scope::GetSymbol() const {
  return symbol_         ? symbol_
      : derivedTypeSpec_ ? &derivedTypeSpec_->typeSymbol()
                         : nullptr;
}

inline std::optional<SourceName> Scope::GetName() const {
  if (const auto *sym{GetSymbol()}) {
    return sym->name();
  } else {
    return std::nullopt;
  }
}

} // namespace Fortran::semantics
#endif // FORTRAN_SEMANTICS_SCOPE_H_
