//===-- YAMLGenerator.cpp - ClangDoc YAML -----------------------*- 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
//
//===----------------------------------------------------------------------===//
// Implementation of the YAML generator, converting decl info into YAML output.
//===----------------------------------------------------------------------===//

#include "Generators.h"
#include "Representation.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>

using namespace clang::doc;

// These define YAML traits for decoding the listed values within a vector.
LLVM_YAML_IS_SEQUENCE_VECTOR(FieldTypeInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(MemberTypeInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(Reference)
LLVM_YAML_IS_SEQUENCE_VECTOR(Location)
LLVM_YAML_IS_SEQUENCE_VECTOR(CommentInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(EnumValueInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(TemplateParamInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(TypedefInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(BaseRecordInfo)
LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr<CommentInfo>)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>)

namespace llvm {
namespace yaml {

// Enumerations to YAML output.

template <> struct ScalarEnumerationTraits<clang::AccessSpecifier> {
  static void enumeration(IO &IO, clang::AccessSpecifier &Value) {
    IO.enumCase(Value, "Public", clang::AccessSpecifier::AS_public);
    IO.enumCase(Value, "Protected", clang::AccessSpecifier::AS_protected);
    IO.enumCase(Value, "Private", clang::AccessSpecifier::AS_private);
    IO.enumCase(Value, "None", clang::AccessSpecifier::AS_none);
  }
};

template <> struct ScalarEnumerationTraits<clang::TagTypeKind> {
  static void enumeration(IO &IO, clang::TagTypeKind &Value) {
    IO.enumCase(Value, "Struct", clang::TagTypeKind::Struct);
    IO.enumCase(Value, "Interface", clang::TagTypeKind::Interface);
    IO.enumCase(Value, "Union", clang::TagTypeKind::Union);
    IO.enumCase(Value, "Class", clang::TagTypeKind::Class);
    IO.enumCase(Value, "Enum", clang::TagTypeKind::Enum);
  }
};

template <> struct ScalarEnumerationTraits<InfoType> {
  static void enumeration(IO &IO, InfoType &Value) {
    IO.enumCase(Value, "Namespace", InfoType::IT_namespace);
    IO.enumCase(Value, "Record", InfoType::IT_record);
    IO.enumCase(Value, "Function", InfoType::IT_function);
    IO.enumCase(Value, "Enum", InfoType::IT_enum);
    IO.enumCase(Value, "Default", InfoType::IT_default);
  }
};

template <> struct ScalarEnumerationTraits<clang::doc::CommentKind> {
  static void enumeration(IO &IO, clang::doc::CommentKind &Value) {
    IO.enumCase(Value, "FullComment", clang::doc::CommentKind::CK_FullComment);
    IO.enumCase(Value, "ParagraphComment",
                clang::doc::CommentKind::CK_ParagraphComment);
    IO.enumCase(Value, "TextComment", clang::doc::CommentKind::CK_TextComment);
    IO.enumCase(Value, "InlineCommandComment",
                clang::doc::CommentKind::CK_InlineCommandComment);
    IO.enumCase(Value, "HTMLStartTagComment",
                clang::doc::CommentKind::CK_HTMLStartTagComment);
    IO.enumCase(Value, "HTMLEndTagComment",
                clang::doc::CommentKind::CK_HTMLEndTagComment);
    IO.enumCase(Value, "BlockCommandComment",
                clang::doc::CommentKind::CK_BlockCommandComment);
    IO.enumCase(Value, "ParamCommandComment",
                clang::doc::CommentKind::CK_ParamCommandComment);
    IO.enumCase(Value, "TParamCommandComment",
                clang::doc::CommentKind::CK_TParamCommandComment);
    IO.enumCase(Value, "VerbatimBlockComment",
                clang::doc::CommentKind::CK_VerbatimBlockComment);
    IO.enumCase(Value, "VerbatimBlockLineComment",
                clang::doc::CommentKind::CK_VerbatimBlockLineComment);
    IO.enumCase(Value, "VerbatimLineComment",
                clang::doc::CommentKind::CK_VerbatimLineComment);
    IO.enumCase(Value, "Unknown", clang::doc::CommentKind::CK_Unknown);
  }
};

// Scalars to YAML output.
template <unsigned U> struct ScalarTraits<SmallString<U>> {

  static void output(const SmallString<U> &S, void *, llvm::raw_ostream &OS) {
    for (const auto &C : S)
      OS << C;
  }

  static StringRef input(StringRef Scalar, void *, SmallString<U> &Value) {
    Value.assign(Scalar.begin(), Scalar.end());
    return StringRef();
  }

  static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
};

template <> struct ScalarTraits<std::array<unsigned char, 20>> {

  static void output(const std::array<unsigned char, 20> &S, void *,
                     llvm::raw_ostream &OS) {
    OS << toHex(toStringRef(S));
  }

  static StringRef input(StringRef Scalar, void *,
                         std::array<unsigned char, 20> &Value) {
    if (Scalar.size() != 40)
      return "Error: Incorrect scalar size for USR.";
    Value = stringToSymbol(Scalar);
    return StringRef();
  }

  static SymbolID stringToSymbol(llvm::StringRef Value) {
    SymbolID USR;
    std::string HexString = fromHex(Value);
    std::copy(HexString.begin(), HexString.end(), USR.begin());
    return SymbolID(USR);
  }

  static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
};

// Helper functions to map infos to YAML.

static void typeInfoMapping(IO &IO, TypeInfo &I) {
  IO.mapOptional("Type", I.Type, Reference());
}

static void fieldTypeInfoMapping(IO &IO, FieldTypeInfo &I) {
  typeInfoMapping(IO, I);
  IO.mapOptional("Name", I.Name, SmallString<16>());
  IO.mapOptional("DefaultValue", I.DefaultValue, SmallString<16>());
}

static void infoMapping(IO &IO, Info &I) {
  IO.mapRequired("USR", I.USR);
  IO.mapOptional("Name", I.Name, SmallString<16>());
  IO.mapOptional("Path", I.Path, SmallString<128>());
  IO.mapOptional("Namespace", I.Namespace, llvm::SmallVector<Reference, 4>());
  IO.mapOptional("Description", I.Description);
}

static void symbolInfoMapping(IO &IO, SymbolInfo &I) {
  infoMapping(IO, I);
  IO.mapOptional("DefLocation", I.DefLoc, std::optional<Location>());
  IO.mapOptional("Location", I.Loc, llvm::SmallVector<Location, 2>());
}

static void recordInfoMapping(IO &IO, RecordInfo &I) {
  symbolInfoMapping(IO, I);
  IO.mapOptional("TagType", I.TagType);
  IO.mapOptional("IsTypeDef", I.IsTypeDef, false);
  IO.mapOptional("Members", I.Members);
  IO.mapOptional("Bases", I.Bases);
  IO.mapOptional("Parents", I.Parents, llvm::SmallVector<Reference, 4>());
  IO.mapOptional("VirtualParents", I.VirtualParents,
                 llvm::SmallVector<Reference, 4>());
  IO.mapOptional("ChildRecords", I.Children.Records, std::vector<Reference>());
  IO.mapOptional("ChildFunctions", I.Children.Functions);
  IO.mapOptional("ChildEnums", I.Children.Enums);
  IO.mapOptional("ChildTypedefs", I.Children.Typedefs);
  IO.mapOptional("Template", I.Template);
}

static void commentInfoMapping(IO &IO, CommentInfo &I) {
  IO.mapOptional("Kind", I.Kind, CommentKind::CK_Unknown);
  IO.mapOptional("Text", I.Text, SmallString<64>());
  IO.mapOptional("Name", I.Name, SmallString<16>());
  IO.mapOptional("Direction", I.Direction, SmallString<8>());
  IO.mapOptional("ParamName", I.ParamName, SmallString<16>());
  IO.mapOptional("CloseName", I.CloseName, SmallString<16>());
  IO.mapOptional("SelfClosing", I.SelfClosing, false);
  IO.mapOptional("Explicit", I.Explicit, false);
  IO.mapOptional("Args", I.Args, llvm::SmallVector<SmallString<16>, 4>());
  IO.mapOptional("AttrKeys", I.AttrKeys,
                 llvm::SmallVector<SmallString<16>, 4>());
  IO.mapOptional("AttrValues", I.AttrValues,
                 llvm::SmallVector<SmallString<16>, 4>());
  IO.mapOptional("Children", I.Children);
}

// Template specialization to YAML traits for Infos.

template <> struct MappingTraits<Location> {
  static void mapping(IO &IO, Location &Loc) {
    IO.mapOptional("LineNumber", Loc.StartLineNumber, 0);
    IO.mapOptional("Filename", Loc.Filename, SmallString<32>());
  }
};

template <> struct MappingTraits<Reference> {
  static void mapping(IO &IO, Reference &Ref) {
    IO.mapOptional("Type", Ref.RefType, InfoType::IT_default);
    IO.mapOptional("Name", Ref.Name, SmallString<16>());
    IO.mapOptional("QualName", Ref.QualName, SmallString<16>());
    IO.mapOptional("USR", Ref.USR, SymbolID());
    IO.mapOptional("Path", Ref.Path, SmallString<128>());
  }
};

template <> struct MappingTraits<TypeInfo> {
  static void mapping(IO &IO, TypeInfo &I) { typeInfoMapping(IO, I); }
};

template <> struct MappingTraits<FieldTypeInfo> {
  static void mapping(IO &IO, FieldTypeInfo &I) {
    typeInfoMapping(IO, I);
    IO.mapOptional("Name", I.Name, SmallString<16>());
    IO.mapOptional("DefaultValue", I.DefaultValue, SmallString<16>());
  }
};

template <> struct MappingTraits<MemberTypeInfo> {
  static void mapping(IO &IO, MemberTypeInfo &I) {
    fieldTypeInfoMapping(IO, I);
    // clang::AccessSpecifier::AS_none is used as the default here because it's
    // the AS that shouldn't be part of the output. Even though AS_public is the
    // default in the struct, it should be displayed in the YAML output.
    IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none);
    IO.mapOptional("Description", I.Description);
  }
};

template <> struct MappingTraits<NamespaceInfo> {
  static void mapping(IO &IO, NamespaceInfo &I) {
    infoMapping(IO, I);
    IO.mapOptional("ChildNamespaces", I.Children.Namespaces,
                   std::vector<Reference>());
    IO.mapOptional("ChildRecords", I.Children.Records,
                   std::vector<Reference>());
    IO.mapOptional("ChildFunctions", I.Children.Functions);
    IO.mapOptional("ChildEnums", I.Children.Enums);
    IO.mapOptional("ChildTypedefs", I.Children.Typedefs);
  }
};

template <> struct MappingTraits<RecordInfo> {
  static void mapping(IO &IO, RecordInfo &I) { recordInfoMapping(IO, I); }
};

template <> struct MappingTraits<BaseRecordInfo> {
  static void mapping(IO &IO, BaseRecordInfo &I) {
    recordInfoMapping(IO, I);
    IO.mapOptional("IsVirtual", I.IsVirtual, false);
    // clang::AccessSpecifier::AS_none is used as the default here because it's
    // the AS that shouldn't be part of the output. Even though AS_public is the
    // default in the struct, it should be displayed in the YAML output.
    IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none);
    IO.mapOptional("IsParent", I.IsParent, false);
  }
};

template <> struct MappingTraits<EnumValueInfo> {
  static void mapping(IO &IO, EnumValueInfo &I) {
    IO.mapOptional("Name", I.Name);
    IO.mapOptional("Value", I.Value);
    IO.mapOptional("Expr", I.ValueExpr, SmallString<16>());
  }
};

template <> struct MappingTraits<EnumInfo> {
  static void mapping(IO &IO, EnumInfo &I) {
    symbolInfoMapping(IO, I);
    IO.mapOptional("Scoped", I.Scoped, false);
    IO.mapOptional("BaseType", I.BaseType);
    IO.mapOptional("Members", I.Members);
  }
};

template <> struct MappingTraits<TypedefInfo> {
  static void mapping(IO &IO, TypedefInfo &I) {
    symbolInfoMapping(IO, I);
    IO.mapOptional("Underlying", I.Underlying.Type);
    IO.mapOptional("IsUsing", I.IsUsing, false);
  }
};

template <> struct MappingTraits<FunctionInfo> {
  static void mapping(IO &IO, FunctionInfo &I) {
    symbolInfoMapping(IO, I);
    IO.mapOptional("IsMethod", I.IsMethod, false);
    IO.mapOptional("Parent", I.Parent, Reference());
    IO.mapOptional("Params", I.Params);
    IO.mapOptional("ReturnType", I.ReturnType);
    // clang::AccessSpecifier::AS_none is used as the default here because it's
    // the AS that shouldn't be part of the output. Even though AS_public is the
    // default in the struct, it should be displayed in the YAML output.
    IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none);
    IO.mapOptional("Template", I.Template);
  }
};

template <> struct MappingTraits<TemplateParamInfo> {
  static void mapping(IO &IO, TemplateParamInfo &I) {
    IO.mapOptional("Contents", I.Contents);
  }
};

template <> struct MappingTraits<TemplateSpecializationInfo> {
  static void mapping(IO &IO, TemplateSpecializationInfo &I) {
    IO.mapOptional("SpecializationOf", I.SpecializationOf);
    IO.mapOptional("Params", I.Params);
  }
};

template <> struct MappingTraits<TemplateInfo> {
  static void mapping(IO &IO, TemplateInfo &I) {
    IO.mapOptional("Params", I.Params);
    IO.mapOptional("Specialization", I.Specialization,
                   std::optional<TemplateSpecializationInfo>());
  }
};

template <> struct MappingTraits<CommentInfo> {
  static void mapping(IO &IO, CommentInfo &I) { commentInfoMapping(IO, I); }
};

template <> struct MappingTraits<std::unique_ptr<CommentInfo>> {
  static void mapping(IO &IO, std::unique_ptr<CommentInfo> &I) {
    if (I)
      commentInfoMapping(IO, *I);
  }
};

} // end namespace yaml
} // end namespace llvm

namespace clang {
namespace doc {

/// Generator for YAML documentation.
class YAMLGenerator : public Generator {
public:
  static const char *Format;

  llvm::Error generateDocs(StringRef RootDir,
                           llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
                           const ClangDocContext &CDCtx) override;
  llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
                                 const ClangDocContext &CDCtx) override;
};

const char *YAMLGenerator::Format = "yaml";

llvm::Error
YAMLGenerator::generateDocs(StringRef RootDir,
                            llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
                            const ClangDocContext &CDCtx) {
  for (const auto &Group : Infos) {
    doc::Info *Info = Group.getValue().get();

    // Output file names according to the USR except the global namesapce.
    // Anonymous namespaces are taken care of in serialization, so here we can
    // safely assume an unnamed namespace is the global one.
    llvm::SmallString<128> Path;
    llvm::sys::path::native(RootDir, Path);
    if (Info->IT == InfoType::IT_namespace && Info->Name.empty()) {
      llvm::sys::path::append(Path, "index.yaml");
    } else {
      llvm::sys::path::append(Path, Group.getKey() + ".yaml");
    }

    std::error_code FileErr;
    llvm::raw_fd_ostream InfoOS(Path, FileErr, llvm::sys::fs::OF_Text);
    if (FileErr) {
      return llvm::createStringError(FileErr, "Error opening file '%s'",
                                     Path.c_str());
    }

    if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
      return Err;
    }
  }

  return llvm::Error::success();
}

llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
                                              const ClangDocContext &CDCtx) {
  llvm::yaml::Output InfoYAML(OS);
  switch (I->IT) {
  case InfoType::IT_namespace:
    InfoYAML << *static_cast<clang::doc::NamespaceInfo *>(I);
    break;
  case InfoType::IT_record:
    InfoYAML << *static_cast<clang::doc::RecordInfo *>(I);
    break;
  case InfoType::IT_enum:
    InfoYAML << *static_cast<clang::doc::EnumInfo *>(I);
    break;
  case InfoType::IT_function:
    InfoYAML << *static_cast<clang::doc::FunctionInfo *>(I);
    break;
  case InfoType::IT_typedef:
    InfoYAML << *static_cast<clang::doc::TypedefInfo *>(I);
    break;
  case InfoType::IT_concept:
  case InfoType::IT_variable:
  case InfoType::IT_friend:
    break;
  case InfoType::IT_default:
    return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                   "unexpected InfoType");
  }
  return llvm::Error::success();
}

static GeneratorRegistry::Add<YAMLGenerator> YAML(YAMLGenerator::Format,
                                                  "Generator for YAML output.");

// This anchor is used to force the linker to link in the generated object file
// and thus register the generator.
volatile int YAMLGeneratorAnchorSource = 0;

} // namespace doc
} // namespace clang
