//
// This file is distributed under the MIT License. See LICENSE.md for details.
//

#define BOOST_TEST_MODULE location
bool init_unit_test();
#include "boost/test/unit_test.hpp"

#include "revng/Model/Binary.h"
#include "revng/Pipeline/Location.h"
#include "revng/Pipes/Ranks.h"
#include "revng/Support/BasicBlockID.h"

BOOST_AUTO_TEST_SUITE(RevngLocationInfrastructure);

BOOST_AUTO_TEST_CASE(DefinitionStyles) {
  namespace ranks = revng::ranks;

  const auto A0 = MetaAddress::fromString("0x1:Generic64");
  const auto A1 = BasicBlockID::fromString("0x2:Generic64");
  const auto A2 = MetaAddress::fromString("0x3:Generic64");
  auto Location = pipeline::location(ranks::Instruction, A0, A1, A2);

  constexpr auto S = "/instruction/0x1:Generic64/0x2:Generic64/0x3:Generic64";
  auto MaybeInstruction = pipeline::locationFromString(ranks::Instruction, S);
  revng_check(MaybeInstruction.has_value());

  revng_check(Location.tuple() == MaybeInstruction->tuple());
  revng_check(Location.toString() == S);
}

BOOST_AUTO_TEST_CASE(MetaAddressAsTheKey) {
  namespace ranks = revng::ranks;

  const auto A0 = MetaAddress::fromString("0x123:Generic64");
  const auto A1 = BasicBlockID::fromString("0x456:Generic64");
  const auto A2 = MetaAddress::fromString("0x789:Generic64");
  auto Location = pipeline::location(ranks::Instruction, A0, A1, A2);

  revng_check(std::get<0>(Location.at(ranks::Function)).address() == 0x123);
  revng_check(Location.at(ranks::BasicBlock).start().address() == 0x456);
  revng_check(Location.at(ranks::Instruction).address() == 0x789);

  constexpr auto Expected = "/instruction/0x123:Generic64/"
                            "0x456:Generic64/0x789:Generic64";
  revng_check(Location.toString() == Expected);
  revng_check(Location.toString() == toString(Location));
}

BOOST_AUTO_TEST_CASE(TypeIDAsTheKey) {
  TupleTree<model::Binary> NewModel;

  // Define a new union
  auto [Definition, _] = NewModel->makeUnionDefinition();
  Definition.CustomName() = "my_cool_union";
  auto &ThirdField = Definition.Fields()[2];
  ThirdField.CustomName() = "third_field";

  // Introduce a location of a field by embedding the type definition key and
  // the field index.
  namespace ranks = revng::ranks;
  auto FieldLocation = pipeline::location(ranks::UnionField,
                                          Definition.key(),
                                          2);

  // Extract a type back from the location by finding the relevant key
  // in the binary.
  auto Type = NewModel->makeType(FieldLocation.at(ranks::TypeDefinition));

  // Ensure the type we got is the same type we started with.
  revng_check(Type->tryGetAsDefinition()->CustomName() == "my_cool_union");

  // Ensure the key is encoded in the serialized form of the location.
  std::string Key = toString(Definition.key());
  revng_check(FieldLocation.toString() == "/union-field/" + Key + "/2");
  revng_check(FieldLocation.toString() == toString(FieldLocation));
}

BOOST_AUTO_TEST_CASE(Serialization) {
  constexpr std::array<llvm::StringRef, 4> TestCases{
    // a list of unrelated serialized locations to use as sources of truth
    "/binary",
    "/instruction/0x12:Generic64/0x34:Generic64/0x56:Generic64",
    "/type-definition/1026-TypedefDefinition",
    "/raw-byte-range/0x78:Generic64/0x90:Generic64"
  };

  using pipeline::locationFromString;
  namespace ranks = revng::ranks;
  for (auto TestCase : TestCases) {
    bool ParsedOnce = false;

    if (auto Binary = locationFromString(ranks::Binary, TestCase)) {
      revng_check(TestCase == TestCases[0]);
      revng_check(ParsedOnce == false);
      ParsedOnce = true;

      revng_check(TestCase == Binary->toString());
    }

    if (auto Instruction = locationFromString(ranks::Instruction, TestCase)) {
      revng_check(TestCase == TestCases[1]);
      revng_check(ParsedOnce == false);
      ParsedOnce = true;

      auto FunctionMetaAddress = std::get<0>(Instruction->at(ranks::Function));
      revng_check(FunctionMetaAddress.address() == 0x12);
      revng_check(Instruction->at(ranks::BasicBlock).start().address() == 0x34);
      revng_check(Instruction->at(ranks::Instruction).address() == 0x56);

      revng_check(TestCase == Instruction->toString());
    }

    if (auto Type = locationFromString(ranks::TypeDefinition, TestCase)) {
      revng_check(TestCase == TestCases[2]);
      revng_check(ParsedOnce == false);
      ParsedOnce = true;

      revng_check(std::get<uint64_t>(Type->at(ranks::TypeDefinition)) == 1026);

      revng_check(TestCase == Type->toString());
    }

    if (auto RawRange = locationFromString(ranks::RawByteRange, TestCase)) {
      revng_check(TestCase == TestCases[3]);
      revng_check(ParsedOnce == false);
      ParsedOnce = true;

      revng_check(RawRange->at(ranks::RawByte).address() == 0x78);
      revng_check(RawRange->at(ranks::RawByteRange).address() == 0x90);

      revng_check(TestCase == RawRange->toString());
    }

    revng_check(!locationFromString(ranks::Function, TestCase).has_value());
    revng_check(!locationFromString(ranks::BasicBlock, TestCase).has_value());
    revng_check(!locationFromString(ranks::StructField, TestCase).has_value());
    revng_check(!locationFromString(ranks::UnionField, TestCase).has_value());
    revng_check(!locationFromString(ranks::EnumEntry, TestCase).has_value());
    revng_check(!locationFromString(ranks::RawByte, TestCase).has_value());

    using namespace std::string_literals;
    revng_check(ParsedOnce,
                ("Parsing of '"s + TestCase.data() + "' failed").c_str());
  }
}

BOOST_AUTO_TEST_SUITE_END();
