// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef IR_PROTOBUF_
#define IR_PROTOBUF_

#include <ir_representation.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wnested-anon-types"
#include "proto/abi_dump.pb.h"
#include "proto/abi_diff.pb.h"
#pragma clang diagnostic pop

#include <google/protobuf/text_format.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>


// Classes which act as middle-men between clang AST parsing routines and
// message format specific dumpers.
namespace abi_util {

inline abi_diff::CompatibilityStatus CompatibilityStatusIRToProtobuf(
    CompatibilityStatusIR status) {
  switch(status) {
    case CompatibilityStatusIR::Incompatible:
      return abi_diff::CompatibilityStatus::INCOMPATIBLE;
    case CompatibilityStatusIR::Extension:
      return abi_diff::CompatibilityStatus::EXTENSION;
    default:
      break;
  }
  return abi_diff::CompatibilityStatus::COMPATIBLE;
}

inline abi_dump::AccessSpecifier AccessIRToProtobuf(AccessSpecifierIR access) {
  switch (access) {
    case AccessSpecifierIR::ProtectedAccess:
      return abi_dump::AccessSpecifier::protected_access;
    case AccessSpecifierIR::PrivateAccess:
      return abi_dump::AccessSpecifier::private_access;
    default:
      return abi_dump::AccessSpecifier::public_access;
  }
  return abi_dump::AccessSpecifier::public_access;
}

inline AccessSpecifierIR AccessProtobufToIR(
    abi_dump::AccessSpecifier access) {
  switch (access) {
    case abi_dump::AccessSpecifier::protected_access:
      return AccessSpecifierIR::ProtectedAccess;
    case abi_dump::AccessSpecifier::private_access:
      return AccessSpecifierIR::PrivateAccess;
    default:
      return AccessSpecifierIR::PublicAccess;
  }
  return AccessSpecifierIR::PublicAccess;
}

inline abi_dump::RecordKind RecordKindIRToProtobuf(
    RecordTypeIR::RecordKind kind) {
  switch (kind) {
    case RecordTypeIR::RecordKind::struct_kind:
      return abi_dump::RecordKind::struct_kind;

    case RecordTypeIR::RecordKind::class_kind:
      return abi_dump::RecordKind::class_kind;

    case RecordTypeIR::RecordKind::union_kind:
      return abi_dump::RecordKind::union_kind;

    default:
      return abi_dump::RecordKind::struct_kind;
  }
  // Should not be reached
  assert(false);
}

inline RecordTypeIR::RecordKind RecordKindProtobufToIR(
    abi_dump::RecordKind kind) {
  switch (kind) {
    case abi_dump::RecordKind::struct_kind:
      return RecordTypeIR::struct_kind;

    case abi_dump::RecordKind::class_kind:
      return RecordTypeIR::class_kind;

    case abi_dump::RecordKind::union_kind:
      return RecordTypeIR::union_kind;

    default:
      return RecordTypeIR::struct_kind;
  }
  // Should not be reached
  assert(false);
}

inline abi_dump::VTableComponent::Kind VTableComponentKindIRToProtobuf(
    VTableComponentIR::Kind kind) {
  switch (kind) {
    case VTableComponentIR::Kind::VCallOffset:
      return abi_dump::VTableComponent_Kind_VCallOffset;

    case VTableComponentIR::Kind::VBaseOffset:
      return abi_dump::VTableComponent_Kind_VBaseOffset;

    case VTableComponentIR::Kind::OffsetToTop:
      return abi_dump::VTableComponent_Kind_OffsetToTop;

    case VTableComponentIR::Kind::RTTI:
      return abi_dump::VTableComponent_Kind_RTTI;

    case VTableComponentIR::Kind::FunctionPointer:
      return abi_dump::VTableComponent_Kind_FunctionPointer;

    case VTableComponentIR::Kind::CompleteDtorPointer:
      return abi_dump::VTableComponent_Kind_CompleteDtorPointer;

    case VTableComponentIR::Kind::DeletingDtorPointer:
      return abi_dump::VTableComponent_Kind_DeletingDtorPointer;

    default:
      return abi_dump::VTableComponent_Kind_UnusedFunctionPointer;
  }
  // Should not be reached
  assert(false);
}

inline VTableComponentIR::Kind VTableComponentKindProtobufToIR(
    abi_dump::VTableComponent_Kind kind) {
  switch (kind) {
    case abi_dump::VTableComponent_Kind_VCallOffset:
      return VTableComponentIR::Kind::VCallOffset;

    case abi_dump::VTableComponent_Kind_VBaseOffset:
      return VTableComponentIR::Kind::VBaseOffset;

    case abi_dump::VTableComponent_Kind_OffsetToTop:
      return VTableComponentIR::Kind::OffsetToTop;

    case abi_dump::VTableComponent_Kind_RTTI:
      return VTableComponentIR::Kind::RTTI;

    case abi_dump::VTableComponent_Kind_FunctionPointer:
      return VTableComponentIR::Kind::FunctionPointer;

    case abi_dump::VTableComponent_Kind_CompleteDtorPointer:
      return VTableComponentIR::Kind::CompleteDtorPointer;

    case abi_dump::VTableComponent_Kind_DeletingDtorPointer:
      return VTableComponentIR::Kind::DeletingDtorPointer;

    default:
      return VTableComponentIR::Kind::UnusedFunctionPointer;
  }
  // Should not be reached
  assert(false);
}

class IRToProtobufConverter {
 private:
  static bool AddTemplateInformation(
    abi_dump::TemplateInfo *ti, const abi_util::TemplatedArtifactIR *ta);

  static bool AddTypeInfo(
      abi_dump::BasicNamedAndTypedDecl *type_info, const TypeIR *typep);

  static bool AddRecordFields(
      abi_dump::RecordType *record_protobuf, const RecordTypeIR *record_ir);

  static bool AddBaseSpecifiers(
      abi_dump::RecordType *record_protobuf, const RecordTypeIR *record_ir);

  static bool AddVTableLayout(
      abi_dump::RecordType *record_protobuf, const RecordTypeIR *record_ir);

  static bool AddTagTypeInfo(abi_dump::TagType *tag_type_protobuf,
                             const TagTypeIR *tag_type_ir);

  static bool AddEnumFields(abi_dump::EnumType *enum_protobuf,
                            const EnumTypeIR *enum_ir);
 public:
  static abi_dump::EnumType ConvertEnumTypeIR(const EnumTypeIR *enump);

  static abi_dump::RecordType ConvertRecordTypeIR(const RecordTypeIR *recordp);

  static abi_dump::FunctionType ConvertFunctionTypeIR (
      const FunctionTypeIR *function_typep);

  template <typename CFunctionLikeMessage>
  static bool AddFunctionParametersAndSetReturnType(
      CFunctionLikeMessage *function_like_protobuf,
      const CFunctionLikeIR *cfunction_like_ir);

  template <typename CFunctionLikeMessage>
  static bool AddFunctionParameters(CFunctionLikeMessage *function_protobuf,
                                    const CFunctionLikeIR *cfunction_like_ir);

  static abi_dump::FunctionDecl ConvertFunctionIR(const FunctionIR *functionp);

  static abi_dump::GlobalVarDecl ConvertGlobalVarIR(
      const GlobalVarIR *global_varp);

  static abi_dump::PointerType ConvertPointerTypeIR(
      const PointerTypeIR *pointerp);

  static abi_dump::QualifiedType ConvertQualifiedTypeIR(
      const QualifiedTypeIR *qualtypep);

  static abi_dump::BuiltinType ConvertBuiltinTypeIR(
      const BuiltinTypeIR *builtin_typep);

  static abi_dump::ArrayType ConvertArrayTypeIR(
      const ArrayTypeIR *array_typep);

  static abi_dump::LvalueReferenceType ConvertLvalueReferenceTypeIR(
      const LvalueReferenceTypeIR *lvalue_reference_typep);

  static abi_dump::RvalueReferenceType ConvertRvalueReferenceTypeIR(
      const RvalueReferenceTypeIR *rvalue_reference_typep);

  static abi_dump::ElfFunction ConvertElfFunctionIR(
      const ElfFunctionIR *elf_function_ir);

  static abi_dump::ElfObject ConvertElfObjectIR(
      const ElfObjectIR *elf_object_ir);
};

class IRDiffToProtobufConverter {
 private:
  static bool AddTypeInfoDiff(abi_diff::TypeInfoDiff *type_info_diff_protobuf,
                              const TypeDiffIR *type_diff_ir);

  static bool AddVTableLayoutDiff(
    abi_diff::VTableLayoutDiff *vtable_layout_diff_protobuf,
    const VTableLayoutDiffIR *vtable_layout_diff_ir);

  static bool AddBaseSpecifierDiffs(
    abi_diff::CXXBaseSpecifierDiff *base_specifier_diff_protobuf,
    const CXXBaseSpecifierDiffIR *base_specifier_diff_ir);

  static bool AddRecordFields(
    abi_diff::RecordTypeDiff *record_diff_protobuf,
    const std::vector<const RecordFieldIR *> &record_fields_removed_ir,
    bool removed);

  static bool AddRecordFieldDiffs(
    abi_diff::RecordTypeDiff *record_diff_protobuf,
    const std::vector<RecordFieldDiffIR> &record_field_diff_ir);

  static bool AddEnumUnderlyingTypeDiff(
    abi_diff::UnderlyingTypeDiff *underlying_type_diff_protobuf,
    const std::pair<std::string, std::string> *underlying_type_diff_ir);

 public:
  static abi_diff::RecordTypeDiff ConvertRecordTypeDiffIR(
      const RecordTypeDiffIR *record_type_diffp);

  static abi_diff::EnumTypeDiff ConvertEnumTypeDiffIR(
      const EnumTypeDiffIR *enum_type_diffp);

  static abi_diff::FunctionDeclDiff ConvertFunctionDiffIR(
      const FunctionDiffIR *function_diffp);

  static abi_diff::GlobalVarDeclDiff ConvertGlobalVarDiffIR(
      const GlobalVarDiffIR *global_var_diffp);
};

class ProtobufIRDumper : public IRDumper, public IRToProtobufConverter {
 private:
  // Types
  bool AddRecordTypeIR(const RecordTypeIR *);

  bool AddEnumTypeIR(const EnumTypeIR *);

  bool AddPointerTypeIR(const PointerTypeIR *);

  bool AddQualifiedTypeIR(const QualifiedTypeIR *);

  bool AddLvalueReferenceTypeIR(const LvalueReferenceTypeIR *);

  bool AddRvalueReferenceTypeIR(const RvalueReferenceTypeIR *);

  bool AddArrayTypeIR(const ArrayTypeIR *);

  bool AddBuiltinTypeIR(const BuiltinTypeIR *);

  bool AddFunctionTypeIR(const FunctionTypeIR *function_typep);

  // Functions and global variables.
  bool AddFunctionIR(const FunctionIR *);

  bool AddGlobalVarIR(const GlobalVarIR *);

  bool AddElfFunctionIR(const ElfFunctionIR *);

  bool AddElfObjectIR(const ElfObjectIR *);

 public:
  ProtobufIRDumper(const std::string &dump_path)
      : IRDumper(dump_path), tu_ptr_(new abi_dump::TranslationUnit()) { }

  bool AddLinkableMessageIR(const LinkableMessageIR *) override;

  bool AddElfSymbolMessageIR(const ElfSymbolIR *) override;

  bool Dump() override;

  ~ProtobufIRDumper() override { }

 private:
  std::unique_ptr<abi_dump::TranslationUnit> tu_ptr_;
};


class ProtobufTextFormatToIRReader : public TextFormatToIRReader {
 public:

  ProtobufTextFormatToIRReader(const std::set<std::string> *exported_headers)
      : TextFormatToIRReader(exported_headers) { }

  bool ReadDump(const std::string &dump_file) override;

 private:
  void ReadFunctions(const abi_dump::TranslationUnit &tu);

  void ReadGlobalVariables(const abi_dump::TranslationUnit &tu);

  void ReadEnumTypes(const abi_dump::TranslationUnit &tu);

  void ReadRecordTypes(const abi_dump::TranslationUnit &tu);

  void ReadFunctionTypes(const abi_dump::TranslationUnit &tu);

  void ReadPointerTypes(const abi_dump::TranslationUnit &tu);

  void ReadBuiltinTypes(const abi_dump::TranslationUnit &tu);

  void ReadQualifiedTypes(const abi_dump::TranslationUnit &tu);

  void ReadArrayTypes(const abi_dump::TranslationUnit &tu);

  void ReadLvalueReferenceTypes(const abi_dump::TranslationUnit &tu);

  void ReadRvalueReferenceTypes(const abi_dump::TranslationUnit &tu);

  void ReadElfFunctions (const abi_dump::TranslationUnit &tu);

  void ReadElfObjects (const abi_dump::TranslationUnit &tu);

  void ReadTypeInfo(const abi_dump::BasicNamedAndTypedDecl &type_info,
                    TypeIR *typep);

  FunctionIR FunctionProtobufToIR(const abi_dump::FunctionDecl &);

  FunctionTypeIR FunctionTypeProtobufToIR(
      const abi_dump::FunctionType &function_type_protobuf);

  RecordTypeIR RecordTypeProtobufToIR(
       const abi_dump::RecordType &record_type_protobuf);

  std::vector<RecordFieldIR> RecordFieldsProtobufToIR(
    const google::protobuf::RepeatedPtrField<abi_dump::RecordFieldDecl> &rfp);

  std::vector<CXXBaseSpecifierIR> RecordCXXBaseSpecifiersProtobufToIR(
    const google::protobuf::RepeatedPtrField<abi_dump::CXXBaseSpecifier> &rbs);

  std::vector<EnumFieldIR> EnumFieldsProtobufToIR(
       const google::protobuf::RepeatedPtrField<abi_dump::EnumFieldDecl> &efp);

  EnumTypeIR EnumTypeProtobufToIR(
       const abi_dump::EnumType &enum_type_protobuf);

  VTableLayoutIR VTableLayoutProtobufToIR(
    const abi_dump::VTableLayout &vtable_layout_protobuf);

  TemplateInfoIR TemplateInfoProtobufToIR(
       const abi_dump::TemplateInfo &template_info_protobuf);
};

class ProtobufIRDiffDumper : public IRDiffDumper {
 public:
  ProtobufIRDiffDumper(const std::string &dump_path)
      : IRDiffDumper(dump_path),
        diff_tu_(new abi_diff::TranslationUnitDiff()) { }

  bool AddDiffMessageIR(const DiffMessageIR *, const std::string &type_stack,
                        DiffKind diff_kind) override;

  bool AddLinkableMessageIR(const LinkableMessageIR *,
                            DiffKind diff_kind) override;

  bool AddElfSymbolMessageIR(const ElfSymbolIR *, DiffKind diff_kind) override;

  void AddLibNameIR(const std::string &name) override;

  void AddArchIR(const std::string &arch) override;

  void AddCompatibilityStatusIR(CompatibilityStatusIR status) override;

  bool Dump() override;

   CompatibilityStatusIR GetCompatibilityStatusIR() override;

   ~ProtobufIRDiffDumper() override { }

 private:
  // User defined types.
  bool AddRecordTypeDiffIR(const RecordTypeDiffIR *,
                           const std::string &type_stack, DiffKind diff_kind);

  bool AddEnumTypeDiffIR(const EnumTypeDiffIR *,
                         const std::string &type_stack, DiffKind diff_kind);

  // Functions and global variables.
  bool AddFunctionDiffIR(const FunctionDiffIR *,
                         const std::string &type_stack, DiffKind diff_kind);

  bool AddGlobalVarDiffIR(const GlobalVarDiffIR *,
                          const std::string &type_stack, DiffKind diff_kind);

  bool AddLoneRecordTypeDiffIR(const RecordTypeIR *, DiffKind diff_kind);

  bool AddLoneEnumTypeDiffIR(const EnumTypeIR *, DiffKind diff_kind);

  // Functions and global variables.
  bool AddLoneFunctionDiffIR(const FunctionIR *, DiffKind diff_kind);

  bool AddLoneGlobalVarDiffIR(const GlobalVarIR *, DiffKind diff_kind);

  bool AddElfObjectIR(const ElfObjectIR *elf_object_ir, DiffKind diff_kind);

  bool AddElfFunctionIR(const ElfFunctionIR *elf_function_ir,
                        DiffKind diff_kind);

 protected:
  std::unique_ptr<abi_diff::TranslationUnitDiff> diff_tu_;
};

} // abi_util

#endif // IR_PROTOBUF_