//===-- UdtRecordCompleterTests.cpp ---------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "Plugins/SymbolFile/NativePDB/UdtRecordCompleter.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

using namespace lldb_private::npdb;
using namespace llvm;

namespace {
using Member = UdtRecordCompleter::Member;
using MemberUP = std::unique_ptr<Member>;
using Record = UdtRecordCompleter::Record;

class WrappedMember {
public:
  WrappedMember(const Member &obj) : m_obj(obj) {}

private:
  const Member &m_obj;

  friend bool operator==(const WrappedMember &lhs, const WrappedMember &rhs) {
    return lhs.m_obj.kind == rhs.m_obj.kind &&
           lhs.m_obj.name == rhs.m_obj.name &&
           lhs.m_obj.bit_offset == rhs.m_obj.bit_offset &&
           lhs.m_obj.bit_size == rhs.m_obj.bit_size &&
           lhs.m_obj.base_offset == rhs.m_obj.base_offset &&
           std::equal(lhs.m_obj.fields.begin(), lhs.m_obj.fields.end(),
                      rhs.m_obj.fields.begin(), rhs.m_obj.fields.end(),
                      [](const MemberUP &lhs, const MemberUP &rhs) {
                        return WrappedMember(*lhs) == WrappedMember(*rhs);
                      });
  }

  friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
                                       const WrappedMember &w) {
    os << llvm::formatv("Member{.kind={0}, .name=\"{1}\", .bit_offset={2}, "
                        ".bit_size={3}, .base_offset={4}, .fields=[",
                        w.m_obj.kind, w.m_obj.name, w.m_obj.bit_offset,
                        w.m_obj.bit_size, w.m_obj.base_offset);
    llvm::ListSeparator sep;
    for (auto &f : w.m_obj.fields)
      os << sep << WrappedMember(*f);
    return os << "]}";
  }
};

class WrappedRecord {
public:
  WrappedRecord(const Record &obj) : m_obj(obj) {}

private:
  const Record &m_obj;

  friend bool operator==(const WrappedRecord &lhs, const WrappedRecord &rhs) {
    return lhs.m_obj.start_offset == rhs.m_obj.start_offset &&
           std::equal(
               lhs.m_obj.record.fields.begin(), lhs.m_obj.record.fields.end(),
               rhs.m_obj.record.fields.begin(), rhs.m_obj.record.fields.end(),
               [](const MemberUP &lhs, const MemberUP &rhs) {
                 return WrappedMember(*lhs) == WrappedMember(*rhs);
               });
  }

  friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
                                       const WrappedRecord &w) {
    os << llvm::formatv("Record{.start_offset={0}, .record.fields=[",
                        w.m_obj.start_offset);
    llvm::ListSeparator sep;
    for (const MemberUP &f : w.m_obj.record.fields)
      os << sep << WrappedMember(*f);
    return os << "]}";
  }
};

class UdtRecordCompleterRecordTests : public testing::Test {
protected:
  Record record;

public:
  void SetKind(Member::Kind kind) { record.record.kind = kind; }
  void CollectMember(StringRef name, uint64_t byte_offset, uint64_t byte_size) {
    record.CollectMember(name, byte_offset * 8, byte_size * 8,
                         clang::QualType(), lldb::eAccessPublic, 0);
  }
  void ConstructRecord() { record.ConstructRecord(); }
};
Member *AddField(Member *member, StringRef name, uint64_t byte_offset,
                 uint64_t byte_size, Member::Kind kind,
                 uint64_t base_offset = 0) {
  auto field =
      std::make_unique<Member>(name, byte_offset * 8, byte_size * 8,
                               clang::QualType(), lldb::eAccessPublic, 0);
  field->kind = kind;
  field->base_offset = base_offset;
  member->fields.push_back(std::move(field));
  return member->fields.back().get();
}
} // namespace

TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) {
  SetKind(Member::Kind::Struct);
  CollectMember("m1", 0, 4);
  CollectMember("m2", 0, 4);
  CollectMember("m3", 0, 1);
  CollectMember("m4", 0, 8);
  ConstructRecord();

  // struct {
  //   union {
  //       m1;
  //       m2;
  //       m3;
  //       m4;
  //   };
  // };
  Record record;
  record.start_offset = 0;
  Member *u = AddField(&record.record, "", 0, 0, Member::Union);
  AddField(u, "m1", 0, 4, Member::Field);
  AddField(u, "m2", 0, 4, Member::Field);
  AddField(u, "m3", 0, 1, Member::Field);
  AddField(u, "m4", 0, 8, Member::Field);
  EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record));
}

TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInUnion) {
  SetKind(Member::Kind::Union);
  CollectMember("m1", 0, 4);
  CollectMember("m2", 0, 4);
  CollectMember("m3", 0, 1);
  CollectMember("m4", 0, 8);
  ConstructRecord();

  // union {
  //   m1;
  //   m2;
  //   m3;
  //   m4;
  // };
  Record record;
  record.start_offset = 0;
  AddField(&record.record, "m1", 0, 4, Member::Field);
  AddField(&record.record, "m2", 0, 4, Member::Field);
  AddField(&record.record, "m3", 0, 1, Member::Field);
  AddField(&record.record, "m4", 0, 8, Member::Field);
  EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record));
}

TEST_F(UdtRecordCompleterRecordTests, TestAnonymousStructInUnion) {
  SetKind(Member::Kind::Union);
  CollectMember("m1", 0, 4);
  CollectMember("m2", 4, 4);
  CollectMember("m3", 8, 1);
  ConstructRecord();

  // union {
  //   struct {
  //     m1;
  //     m2;
  //     m3;
  //   };
  // };
  Record record;
  record.start_offset = 0;
  Member *s = AddField(&record.record, "", 0, 0, Member::Kind::Struct);
  AddField(s, "m1", 0, 4, Member::Field);
  AddField(s, "m2", 4, 4, Member::Field);
  AddField(s, "m3", 8, 1, Member::Field);
  EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record));
}

TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInStruct) {
  SetKind(Member::Kind::Struct);
  CollectMember("m1", 0, 4);
  CollectMember("m2", 0, 2);
  CollectMember("m3", 0, 2);
  CollectMember("m4", 2, 4);
  CollectMember("m5", 3, 2);
  ConstructRecord();

  // struct {
  //   union {
  //       m1;
  //       struct {
  //           m2;
  //           m5;
  //       };
  //       struct {
  //           m3;
  //           m4;
  //       };
  //   };
  // };
  Record record;
  record.start_offset = 0;
  Member *u = AddField(&record.record, "", 0, 0, Member::Union);
  AddField(u, "m1", 0, 4, Member::Field);
  Member *s1 = AddField(u, "", 0, 0, Member::Struct);
  Member *s2 = AddField(u, "", 0, 0, Member::Struct);
  AddField(s1, "m2", 0, 2, Member::Field);
  AddField(s1, "m5", 3, 2, Member::Field);
  AddField(s2, "m3", 0, 2, Member::Field);
  AddField(s2, "m4", 2, 4, Member::Field);
  EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record));
}

TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInUnion) {
  SetKind(Member::Kind::Union);
  CollectMember("m1", 0, 4);
  CollectMember("m2", 0, 2);
  CollectMember("m3", 0, 2);
  CollectMember("m4", 2, 4);
  CollectMember("m5", 3, 2);
  ConstructRecord();

  // union {
  //   m1;
  //   struct {
  //       m2;
  //       m5;
  //   };
  //   struct {
  //       m3;
  //       m4;
  //   };
  // };
  Record record;
  record.start_offset = 0;
  AddField(&record.record, "m1", 0, 4, Member::Field);
  Member *s1 = AddField(&record.record, "", 0, 0, Member::Struct);
  Member *s2 = AddField(&record.record, "", 0, 0, Member::Struct);
  AddField(s1, "m2", 0, 2, Member::Field);
  AddField(s1, "m5", 3, 2, Member::Field);
  AddField(s2, "m3", 0, 2, Member::Field);
  AddField(s2, "m4", 2, 4, Member::Field);
  EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record));
}
