///===-- Representation.h - ClangDoc Representation -------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the internal representations of different declaration
// types for the clang-doc tool.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H

#include "clang/AST/Type.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Tooling/Execution.h"
#include "llvm/ADT/SmallVector.h"
#include <array>
#include <optional>
#include <string>

namespace clang {
namespace doc {

// SHA1'd hash of a USR.
using SymbolID = std::array<uint8_t, 20>;

constexpr SymbolID GlobalNamespaceID = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

struct BaseRecordInfo;
struct EnumInfo;
struct FunctionInfo;
struct Info;
struct TypedefInfo;
struct ConceptInfo;
struct VarInfo;

enum class InfoType {
  IT_default,
  IT_namespace,
  IT_record,
  IT_function,
  IT_enum,
  IT_typedef,
  IT_concept,
  IT_variable,
  IT_friend
};

enum class CommentKind {
  CK_FullComment,
  CK_ParagraphComment,
  CK_TextComment,
  CK_InlineCommandComment,
  CK_HTMLStartTagComment,
  CK_HTMLEndTagComment,
  CK_BlockCommandComment,
  CK_ParamCommandComment,
  CK_TParamCommandComment,
  CK_VerbatimBlockComment,
  CK_VerbatimBlockLineComment,
  CK_VerbatimLineComment,
  CK_Unknown
};

CommentKind stringToCommentKind(llvm::StringRef KindStr);
llvm::StringRef commentKindToString(CommentKind Kind);

// A representation of a parsed comment.
struct CommentInfo {
  CommentInfo() = default;
  CommentInfo(CommentInfo &Other) = delete;
  CommentInfo(CommentInfo &&Other) = default;
  CommentInfo &operator=(CommentInfo &&Other) = default;

  bool operator==(const CommentInfo &Other) const;

  // This operator is used to sort a vector of CommentInfos.
  // No specific order (attributes more important than others) is required. Any
  // sort is enough, the order is only needed to call std::unique after sorting
  // the vector.
  bool operator<(const CommentInfo &Other) const;

  std::vector<std::unique_ptr<CommentInfo>>
      Children;              // List of child comments for this CommentInfo.
  SmallString<8> Direction;  // Parameter direction (for (T)ParamCommand).
  SmallString<16> Name;      // Name of the comment (for Verbatim and HTML).
  SmallString<16> ParamName; // Parameter name (for (T)ParamCommand).
  SmallString<16> CloseName; // Closing tag name (for VerbatimBlock).
  SmallString<64> Text;      // Text of the comment.
  llvm::SmallVector<SmallString<16>, 4>
      AttrKeys; // List of attribute keys (for HTML).
  llvm::SmallVector<SmallString<16>, 4>
      AttrValues; // List of attribute values for each key (for HTML).
  llvm::SmallVector<SmallString<16>, 4>
      Args; // List of arguments to commands (for InlineCommand).
  CommentKind Kind = CommentKind::
      CK_Unknown; // Kind of comment (FullComment, ParagraphComment,
                  // TextComment, InlineCommandComment, HTMLStartTagComment,
                  // HTMLEndTagComment, BlockCommandComment,
                  // ParamCommandComment, TParamCommandComment,
                  // VerbatimBlockComment, VerbatimBlockLineComment,
                  // VerbatimLineComment).
  bool SelfClosing = false; // Indicates if tag is self-closing (for HTML).
  bool Explicit = false;    // Indicates if the direction of a param is explicit
                            // (for (T)ParamCommand).
};

struct Reference {
  // This variant (that takes no qualified name parameter) uses the Name as the
  // QualName (very useful in unit tests to reduce verbosity). This can't use an
  // empty string to indicate the default because we need to accept the empty
  // string as a valid input for the global namespace (it will have
  // "GlobalNamespace" as the name, but an empty QualName).
  Reference(SymbolID USR = SymbolID(), StringRef Name = StringRef(),
            InfoType IT = InfoType::IT_default)
      : USR(USR), RefType(IT), Name(Name), QualName(Name) {}
  Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
            StringRef Path = StringRef())
      : USR(USR), RefType(IT), Name(Name), QualName(QualName), Path(Path) {}
  Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
            StringRef Path, SmallString<16> DocumentationFileName)
      : USR(USR), RefType(IT), Name(Name), QualName(QualName), Path(Path),
        DocumentationFileName(DocumentationFileName) {}

  bool operator==(const Reference &Other) const {
    return std::tie(USR, Name, QualName, RefType) ==
           std::tie(Other.USR, Other.Name, QualName, Other.RefType);
  }

  bool mergeable(const Reference &Other);
  void merge(Reference &&I);
  bool operator<(const Reference &Other) const { return Name < Other.Name; }

  /// Returns the path for this Reference relative to CurrentPath.
  llvm::SmallString<64> getRelativeFilePath(const StringRef &CurrentPath) const;

  /// Returns the basename that should be used for this Reference.
  llvm::SmallString<16> getFileBaseName() const;

  SymbolID USR = SymbolID(); // Unique identifier for referenced decl

  InfoType RefType = InfoType::IT_default; // Indicates the type of this
                                           // Reference (namespace, record,
                                           // function, enum, default).

  // Name of type (possibly unresolved). Not including namespaces or template
  // parameters (so for a std::vector<int> this would be "vector"). See also
  // QualName.
  SmallString<16> Name;

  // Full qualified name of this type, including namespaces and template
  // parameter (for example this could be "std::vector<int>"). Contrast to
  // Name.
  SmallString<16> QualName;

  // Path of directory where the clang-doc generated file will be saved
  // (possibly unresolved)
  llvm::SmallString<128> Path;
  SmallString<16> DocumentationFileName;
};

// A Context is a reference that holds a relative path from a certain Info's
// location.
struct Context : public Reference {
  Context(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
          StringRef Path, SmallString<16> DocumentationFileName)
      : Reference(USR, Name, IT, QualName, Path, DocumentationFileName) {}
  explicit Context(const Info &I);
  SmallString<128> RelativePath;
};

// Holds the children of a record or namespace.
struct ScopeChildren {
  // Namespaces and Records are references because they will be properly
  // documented in their own info, while the entirety of Functions and Enums are
  // included here because they should not have separate documentation from
  // their scope.
  //
  // Namespaces are not syntactically valid as children of records, but making
  // this general for all possible container types reduces code complexity.
  std::vector<Reference> Namespaces;
  std::vector<Reference> Records;
  std::vector<FunctionInfo> Functions;
  std::vector<EnumInfo> Enums;
  std::vector<TypedefInfo> Typedefs;
  std::vector<ConceptInfo> Concepts;
  std::vector<VarInfo> Variables;

  void sort();
};

// A base struct for TypeInfos
struct TypeInfo {
  TypeInfo() = default;
  TypeInfo(const Reference &R) : Type(R) {}

  // Convenience constructor for when there is no symbol ID or info type
  // (normally used for built-in types in tests).
  TypeInfo(StringRef Name, StringRef Path = StringRef())
      : Type(SymbolID(), Name, InfoType::IT_default, Name, Path) {}

  bool operator==(const TypeInfo &Other) const { return Type == Other.Type; }

  Reference Type; // Referenced type in this info.

  bool IsTemplate = false;
  bool IsBuiltIn = false;
};

// Represents one template parameter.
//
// This is a very simple serialization of the text of the source code of the
// template parameter. It is saved in a struct so there is a place to add the
// name and default values in the future if needed.
struct TemplateParamInfo {
  TemplateParamInfo() = default;
  explicit TemplateParamInfo(StringRef Contents) : Contents(Contents) {}

  // The literal contents of the code for that specifies this template parameter
  // for this declaration. Typical values will be "class T" and
  // "typename T = int".
  SmallString<16> Contents;
};

struct TemplateSpecializationInfo {
  // Indicates the declaration that this specializes.
  SymbolID SpecializationOf;

  // Template parameters applying to the specialized record/function.
  std::vector<TemplateParamInfo> Params;
};

struct ConstraintInfo {
  ConstraintInfo() = default;
  ConstraintInfo(SymbolID USR, StringRef Name)
      : ConceptRef(USR, Name, InfoType::IT_concept) {}
  Reference ConceptRef;

  SmallString<16> ConstraintExpr;
};

// Records the template information for a struct or function that is a template
// or an explicit template specialization.
struct TemplateInfo {
  // May be empty for non-partial specializations.
  std::vector<TemplateParamInfo> Params;

  // Set when this is a specialization of another record/function.
  std::optional<TemplateSpecializationInfo> Specialization;
  std::vector<ConstraintInfo> Constraints;
};

// Info for field types.
struct FieldTypeInfo : public TypeInfo {
  FieldTypeInfo() = default;
  FieldTypeInfo(const TypeInfo &TI, StringRef Name = StringRef(),
                StringRef DefaultValue = StringRef())
      : TypeInfo(TI), Name(Name), DefaultValue(DefaultValue) {}

  bool operator==(const FieldTypeInfo &Other) const {
    return std::tie(Type, Name, DefaultValue) ==
           std::tie(Other.Type, Other.Name, Other.DefaultValue);
  }

  SmallString<16> Name; // Name associated with this info.

  // When used for function parameters, contains the string representing the
  // expression of the default value, if any.
  SmallString<16> DefaultValue;
};

// Info for member types.
struct MemberTypeInfo : public FieldTypeInfo {
  MemberTypeInfo() = default;
  MemberTypeInfo(const TypeInfo &TI, StringRef Name, AccessSpecifier Access,
                 bool IsStatic = false)
      : FieldTypeInfo(TI, Name), Access(Access), IsStatic(IsStatic) {}

  bool operator==(const MemberTypeInfo &Other) const {
    return std::tie(Type, Name, Access, IsStatic, Description) ==
           std::tie(Other.Type, Other.Name, Other.Access, Other.IsStatic,
                    Other.Description);
  }

  std::vector<CommentInfo> Description;

  // Access level associated with this info (public, protected, private, none).
  // AS_public is set as default because the bitcode writer requires the enum
  // with value 0 to be used as the default.
  // (AS_public = 0, AS_protected = 1, AS_private = 2, AS_none = 3)
  AccessSpecifier Access = AccessSpecifier::AS_public;
  bool IsStatic = false;
};

struct Location {
  Location(int StartLineNumber = 0, int EndLineNumber = 0,
           StringRef Filename = StringRef(), bool IsFileInRootDir = false)
      : Filename(Filename), StartLineNumber(StartLineNumber),
        EndLineNumber(EndLineNumber), IsFileInRootDir(IsFileInRootDir) {}

  bool operator==(const Location &Other) const {
    return std::tie(StartLineNumber, EndLineNumber, Filename) ==
           std::tie(Other.StartLineNumber, Other.EndLineNumber, Other.Filename);
  }

  bool operator!=(const Location &Other) const { return !(*this == Other); }

  // This operator is used to sort a vector of Locations.
  // No specific order (attributes more important than others) is required. Any
  // sort is enough, the order is only needed to call std::unique after sorting
  // the vector.
  bool operator<(const Location &Other) const {
    return std::tie(StartLineNumber, EndLineNumber, Filename) <
           std::tie(Other.StartLineNumber, Other.EndLineNumber, Other.Filename);
  }

  SmallString<32> Filename;
  int StartLineNumber = 0;
  int EndLineNumber = 0;
  bool IsFileInRootDir = false;
};

/// A base struct for Infos.
struct Info {
  Info(InfoType IT = InfoType::IT_default, SymbolID USR = SymbolID(),
       StringRef Name = StringRef(), StringRef Path = StringRef())
      : Path(Path), Name(Name), USR(USR), IT(IT) {}

  Info(const Info &Other) = delete;
  Info(Info &&Other) = default;
  virtual ~Info() = default;

  Info &operator=(Info &&Other) = default;

  void mergeBase(Info &&I);
  bool mergeable(const Info &Other);

  llvm::SmallString<16> extractName() const;

  /// Returns the file path for this Info relative to CurrentPath.
  llvm::SmallString<64> getRelativeFilePath(const StringRef &CurrentPath) const;

  /// Returns the basename that should be used for this Info.
  llvm::SmallString<16> getFileBaseName() const;

  // Path of directory where the clang-doc generated file will be saved.
  llvm::SmallString<128> Path;

  // Unqualified name of the decl.
  SmallString<16> Name;

  // The name used for the file that this info is documented in.
  // In the JSON generator, infos are documented in files with mangled names.
  // Thus, we keep track of the physical filename for linking purposes.
  SmallString<16> DocumentationFileName;

  // List of parent namespaces for this decl.
  llvm::SmallVector<Reference, 4> Namespace;

  // Unique identifier for the decl described by this Info.
  SymbolID USR = SymbolID();

  // Currently only used for namespaces and records.
  SymbolID ParentUSR = SymbolID();

  // InfoType of this particular Info.
  InfoType IT = InfoType::IT_default;

  // Comment description of this decl.
  std::vector<CommentInfo> Description;

  SmallVector<Context, 4> Contexts;
};

inline Context::Context(const Info &I)
    : Reference(I.USR, I.Name, I.IT, I.Name, I.Path, I.DocumentationFileName) {}

// Info for namespaces.
struct NamespaceInfo : public Info {
  NamespaceInfo(SymbolID USR = SymbolID(), StringRef Name = StringRef(),
                StringRef Path = StringRef());

  void merge(NamespaceInfo &&I);

  ScopeChildren Children;
};

// Info for symbols.
struct SymbolInfo : public Info {
  SymbolInfo(InfoType IT, SymbolID USR = SymbolID(),
             StringRef Name = StringRef(), StringRef Path = StringRef())
      : Info(IT, USR, Name, Path) {}

  void merge(SymbolInfo &&I);

  bool operator<(const SymbolInfo &Other) const {
    // Sort by declaration location since we want the doc to be
    // generated in the order of the source code.
    // If the declaration location is the same, or not present
    // we sort by defined location otherwise fallback to the extracted name
    if (Loc.size() > 0 && Other.Loc.size() > 0 && Loc[0] != Other.Loc[0])
      return Loc[0] < Other.Loc[0];

    if (DefLoc && Other.DefLoc && *DefLoc != *Other.DefLoc)
      return *DefLoc < *Other.DefLoc;

    return extractName() < Other.extractName();
  }

  std::optional<Location> DefLoc;     // Location where this decl is defined.
  llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
  SmallString<16> MangledName;
  bool IsStatic = false;
};

struct FriendInfo : SymbolInfo {
  FriendInfo() : SymbolInfo(InfoType::IT_friend) {}
  FriendInfo(SymbolID USR) : SymbolInfo(InfoType::IT_friend, USR) {}
  FriendInfo(const InfoType IT, const SymbolID &USR,
             const StringRef Name = StringRef())
      : SymbolInfo(IT, USR, Name) {}
  bool mergeable(const FriendInfo &Other);
  void merge(FriendInfo &&Other);

  Reference Ref;
  std::optional<TemplateInfo> Template;
  std::optional<TypeInfo> ReturnType;
  std::optional<SmallVector<FieldTypeInfo, 4>> Params;
  bool IsClass = false;
};

struct VarInfo : SymbolInfo {
  VarInfo() : SymbolInfo(InfoType::IT_variable) {}
  explicit VarInfo(SymbolID USR) : SymbolInfo(InfoType::IT_variable, USR) {}

  void merge(VarInfo &&I);

  TypeInfo Type;
};

// TODO: Expand to allow for documenting templating and default args.
// Info for functions.
struct FunctionInfo : public SymbolInfo {
  FunctionInfo(SymbolID USR = SymbolID())
      : SymbolInfo(InfoType::IT_function, USR) {}

  void merge(FunctionInfo &&I);

  Reference Parent;
  TypeInfo ReturnType;
  llvm::SmallVector<FieldTypeInfo, 4> Params;
  SmallString<256> Prototype;

  // When present, this function is a template or specialization.
  std::optional<TemplateInfo> Template;

  // Access level for this method (public, private, protected, none).
  // AS_public is set as default because the bitcode writer requires the enum
  // with value 0 to be used as the default.
  // (AS_public = 0, AS_protected = 1, AS_private = 2, AS_none = 3)
  AccessSpecifier Access = AccessSpecifier::AS_public;

  bool IsMethod = false;
};

// TODO: Expand to allow for documenting templating, inheritance access,
// friend classes
// Info for types.
struct RecordInfo : public SymbolInfo {
  RecordInfo(SymbolID USR = SymbolID(), StringRef Name = StringRef(),
             StringRef Path = StringRef());

  void merge(RecordInfo &&I);

  // Type of this record (struct, class, union, interface).
  TagTypeKind TagType = TagTypeKind::Struct;

  // Indicates if the record was declared using a typedef. Things like anonymous
  // structs in a typedef:
  //   typedef struct { ... } foo_t;
  // are converted into records with the typedef as the Name + this flag set.
  bool IsTypeDef = false;

  // When present, this record is a template or specialization.
  std::optional<TemplateInfo> Template;

  llvm::SmallVector<MemberTypeInfo, 4>
      Members;                             // List of info about record members.
  llvm::SmallVector<Reference, 4> Parents; // List of base/parent records
                                           // (does not include virtual
                                           // parents).
  llvm::SmallVector<Reference, 4>
      VirtualParents; // List of virtual base/parent records.

  std::vector<BaseRecordInfo>
      Bases; // List of base/parent records; this includes inherited methods and
             // attributes

  std::vector<FriendInfo> Friends;

  ScopeChildren Children;
};

// Info for typedef and using statements.
struct TypedefInfo : public SymbolInfo {
  TypedefInfo(SymbolID USR = SymbolID())
      : SymbolInfo(InfoType::IT_typedef, USR) {}

  void merge(TypedefInfo &&I);

  TypeInfo Underlying;

  // Only type aliases can be templates.
  std::optional<TemplateInfo> Template;

  // Underlying type declaration
  SmallString<16> TypeDeclaration;

  // Indicates if this is a new C++ "using"-style typedef:
  //   using MyVector = std::vector<int>
  // False means it's a C-style typedef:
  //   typedef std::vector<int> MyVector;
  bool IsUsing = false;
};

struct BaseRecordInfo : public RecordInfo {
  BaseRecordInfo();
  BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, bool IsVirtual,
                 AccessSpecifier Access, bool IsParent);

  // Access level associated with this inherited info (public, protected,
  // private).
  AccessSpecifier Access = AccessSpecifier::AS_public;
  // Indicates if base corresponds to a virtual inheritance
  bool IsVirtual = false;
  bool IsParent = false; // Indicates if this base is a direct parent
};

// Information for a single possible value of an enumeration.
struct EnumValueInfo {
  explicit EnumValueInfo(StringRef Name = StringRef(),
                         StringRef Value = StringRef("0"),
                         StringRef ValueExpr = StringRef())
      : Name(Name), Value(Value), ValueExpr(ValueExpr) {}

  bool operator==(const EnumValueInfo &Other) const {
    return std::tie(Name, Value, ValueExpr) ==
           std::tie(Other.Name, Other.Value, Other.ValueExpr);
  }

  SmallString<16> Name;

  // The computed value of the enumeration constant. This could be the result of
  // evaluating the ValueExpr, or it could be automatically generated according
  // to C rules.
  SmallString<16> Value;

  // Stores the user-supplied initialization expression for this enumeration
  // constant. This will be empty for implicit enumeration values.
  SmallString<16> ValueExpr;

  /// Comment description of this field.
  std::vector<CommentInfo> Description;
};

// TODO: Expand to allow for documenting templating.
// Info for types.
struct EnumInfo : public SymbolInfo {
  EnumInfo() : SymbolInfo(InfoType::IT_enum) {}
  EnumInfo(SymbolID USR) : SymbolInfo(InfoType::IT_enum, USR) {}

  void merge(EnumInfo &&I);

  // Indicates whether this enum is scoped (e.g. enum class).
  bool Scoped = false;

  // Set to nonempty to the type when this is an explicitly typed enum. For
  //   enum Foo : short { ... };
  // this will be "short".
  std::optional<TypeInfo> BaseType;

  llvm::SmallVector<EnumValueInfo, 4> Members; // List of enum members.
};

struct ConceptInfo : public SymbolInfo {
  ConceptInfo() : SymbolInfo(InfoType::IT_concept) {}
  ConceptInfo(SymbolID USR) : SymbolInfo(InfoType::IT_concept, USR) {}

  void merge(ConceptInfo &&I);

  bool IsType;
  TemplateInfo Template;
  SmallString<16> ConstraintExpression;
};

struct Index : public Reference {
  Index() = default;
  Index(StringRef Name) : Reference(SymbolID(), Name) {}
  Index(StringRef Name, StringRef JumpToSection)
      : Reference(SymbolID(), Name), JumpToSection(JumpToSection) {}
  Index(SymbolID USR, StringRef Name, InfoType IT, StringRef Path)
      : Reference(USR, Name, IT, Name, Path) {}
  // This is used to look for a USR in a vector of Indexes using std::find
  bool operator==(const SymbolID &Other) const { return USR == Other; }
  bool operator<(const Index &Other) const;

  std::optional<SmallString<16>> JumpToSection;
  std::vector<Index> Children;

  void sort();
};

// TODO: Add functionality to include separate markdown pages.

// A standalone function to call to merge a vector of infos into one.
// This assumes that all infos in the vector are of the same type, and will fail
// if they are different.
llvm::Expected<std::unique_ptr<Info>>
mergeInfos(std::vector<std::unique_ptr<Info>> &Values);

struct ClangDocContext {
  ClangDocContext(tooling::ExecutionContext *ECtx, StringRef ProjectName,
                  bool PublicOnly, StringRef OutDirectory, StringRef SourceRoot,
                  StringRef RepositoryUrl, StringRef RepositoryCodeLinePrefix,
                  StringRef Base, std::vector<std::string> UserStylesheets,
                  clang::DiagnosticsEngine &Diags, bool FTimeTrace = false);
  tooling::ExecutionContext *ECtx;
  std::string ProjectName;  // Name of project clang-doc is documenting.
  std::string OutDirectory; // Directory for outputting generated files.
  std::string SourceRoot;   // Directory where processed files are stored. Links
                            // to definition locations will only be generated if
                            // the file is in this dir.
  // URL of repository that hosts code used for links to definition locations.
  std::optional<std::string> RepositoryUrl;
  // Prefix of line code for repository.
  std::optional<std::string> RepositoryLinePrefix;
  // Path of CSS stylesheets that will be copied to OutDirectory and used to
  // style all HTML files.
  std::vector<std::string> UserStylesheets;
  // JavaScript files that will be imported in all HTML files.
  std::vector<std::string> JsScripts;
  // Base directory for remote repositories.
  StringRef Base;
  // Maps mustache template types to specific mustache template files.
  // Ex.    comment-template -> /path/to/comment-template.mustache
  llvm::StringMap<std::string> MustacheTemplates;
  // A pointer to a DiagnosticsEngine for error reporting.
  clang::DiagnosticsEngine &Diags;
  Index Idx;
  int Granularity; // Granularity of ftime trace
  bool PublicOnly; // Indicates if only public declarations are documented.
  bool FTimeTrace; // Indicates if ftime trace is turned on
};

} // namespace doc
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H
