// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2023 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License.  You may obtain a copy of the License at
//
//     https://llvm.org/LICENSE.txt
//
// 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.
//
// Author: Giuliano Procida

#include <cstddef>
#include <filesystem>
#include <iostream>
#include <optional>
#include <vector>

#include <catch2/catch.hpp>
#include <libxml/tree.h>
#include "abigail_reader.h"
#include "equality.h"
#include "graph.h"
#include "runtime.h"

namespace {

std::filesystem::path filename_to_path(const char* f) {
  return std::filesystem::path("testdata") / f;
}

stg::abixml::Document Read(const char* input) {
  stg::Runtime runtime(std::cerr, false);
  return stg::abixml::Read(runtime, filename_to_path(input));
}

stg::Id Read(stg::Graph& graph, const char* input) {
  stg::Runtime runtime(std::cerr, false);
  return stg::abixml::Read(runtime, graph, filename_to_path(input));
}

struct EqualTreeTestCase {
  const char* name;
  const char* left;
  const char* right;
  bool equal;
};

TEST_CASE("EqualTree") {
  const auto test = GENERATE(
      EqualTreeTestCase(
          {"cleaning",
           "abigail_dirty.xml",
           "abigail_clean.xml",
           true}),
      EqualTreeTestCase(
          {"self comparison",
           "abigail_tree_0.xml",
           "abigail_tree_0.xml",
           true}),
      EqualTreeTestCase(
          {"attribute order is irrelevant",
           "abigail_tree_0.xml",
           "abigail_tree_1.xml",
           true}),
      EqualTreeTestCase(
          {"element order is relevant",
           "abigail_tree_0.xml",
           "abigail_tree_2.xml",
           false}),
      EqualTreeTestCase(
          {"attribute missing",
           "abigail_tree_0.xml",
           "abigail_tree_3.xml",
           false}),
      EqualTreeTestCase(
          {"element missing",
           "abigail_tree_0.xml",
           "abigail_tree_4.xml",
           false}),
      EqualTreeTestCase(
          {"attribute changed",
           "abigail_tree_0.xml",
           "abigail_tree_5.xml",
           false}),
      EqualTreeTestCase(
          {"element changed",
           "abigail_tree_0.xml",
           "abigail_tree_6.xml",
           false}));

  SECTION(test.name) {
    const stg::abixml::Document left_document = Read(test.left);
    const stg::abixml::Document right_document = Read(test.right);
    xmlNodePtr left_root = xmlDocGetRootElement(left_document.get());
    xmlNodePtr right_root = xmlDocGetRootElement(right_document.get());
    stg::abixml::Clean(left_root);
    stg::abixml::Clean(right_root);
    CHECK(stg::abixml::EqualTree(left_root, right_root) == test.equal);
    CHECK(stg::abixml::EqualTree(right_root, left_root) == test.equal);
  }
}

struct SubTreeTestCase {
  const char* name;
  const char* left;
  const char* right;
  bool left_sub_right;
  bool right_sub_left;
};

TEST_CASE("SubTree") {
  const auto test = GENERATE(
      SubTreeTestCase(
          {"self comparison",
           "abigail_tree_0.xml",
           "abigail_tree_0.xml",
           true, true}),
      SubTreeTestCase(
          {"attribute missing",
           "abigail_tree_0.xml",
           "abigail_tree_3.xml",
           false, true}),
      SubTreeTestCase(
          {"element missing",
           "abigail_tree_0.xml",
           "abigail_tree_4.xml",
           false, true}),
      SubTreeTestCase(
          {"member-type access special case",
           "abigail_tree_0.xml",
           "abigail_tree_7.xml",
           true, true}));

  SECTION(test.name) {
    const stg::abixml::Document left_document = Read(test.left);
    const stg::abixml::Document right_document = Read(test.right);
    xmlNodePtr left_root = xmlDocGetRootElement(left_document.get());
    xmlNodePtr right_root = xmlDocGetRootElement(right_document.get());
    stg::abixml::Clean(left_root);
    stg::abixml::Clean(right_root);
    CHECK(stg::abixml::SubTree(left_root, right_root) == test.left_sub_right);
    CHECK(stg::abixml::SubTree(right_root, left_root) == test.right_sub_left);
  }
}

struct TidyTestCase {
  const char* name;
  const std::vector<const char*> files;
};

TEST_CASE("Tidy") {
  const auto test = GENERATE(
      TidyTestCase(
          {"bad DWARF ELF link",
           {"abigail_bad_dwarf_elf_link_0.xml",
            "abigail_bad_dwarf_elf_link_1.xml",
            "abigail_bad_dwarf_elf_link_2.xml"}}),
      TidyTestCase(
          {"anonymous type normalisation",
           {"abigail_anonymous_types_0.xml",
            "abigail_anonymous_types_1.xml",
            "abigail_anonymous_types_2.xml",
            "abigail_anonymous_types_3.xml",
            "abigail_anonymous_types_4.xml"}}),
      TidyTestCase(
          {"duplicate data members",
           {"abigail_duplicate_data_members_0.xml",
            "abigail_duplicate_data_members_1.xml"}}),
      TidyTestCase(
          {"duplicate type resolution - exact duplicate",
           {"abigail_duplicate_types_0.xml",
            "abigail_duplicate_types_1.xml"}}),
      TidyTestCase(
          {"duplicate type resolution - partial duplicate",
           {"abigail_duplicate_types_0.xml",
            "abigail_duplicate_types_2.xml"}}),
      TidyTestCase(
          {"duplicate type resolution - multiple partial duplicates",
           {"abigail_duplicate_types_0.xml",
            "abigail_duplicate_types_3.xml"}}),
      TidyTestCase(
          {"duplicate type resolution - no maximal duplicate",
           {"abigail_duplicate_types_4.xml",
            "abigail_duplicate_types_5.xml"}}),
      TidyTestCase(
          {"duplicate type resolution - different scopes",
           {"abigail_duplicate_types_4.xml",
            "abigail_duplicate_types_6.xml"}}),
      TidyTestCase(
          {"duplicate type resolution - stray anonymous member",
           {"abigail_duplicate_types_7.xml",
            "abigail_duplicate_types_8.xml"}}),
      TidyTestCase(
          {"corpus group handling",
           {"abigail_duplicate_types_0.xml",
            "abigail_duplicate_types_9.xml"}}));

  SECTION(test.name) {
    // Read inputs.
    stg::Graph graph;
    std::vector<stg::Id> ids;
    ids.reserve(test.files.size());
    for (const char* file : test.files) {
      ids.push_back(Read(graph, file));
    }

    // Useless equality cache.
    struct NoCache {
      static std::optional<bool> Query(const stg::Pair&) {
        return std::nullopt;
      }
      void AllSame(const std::vector<stg::Pair>&) {}
      void AllDifferent(const std::vector<stg::Pair>&) {}
    };

    // Check exact equality.
    NoCache cache;
    for (size_t ix = 1; ix < ids.size(); ++ix) {
      CHECK(stg::Equals<NoCache>(graph, cache)(ids[0], ids[ix]));
    }
  }
}

}  // namespace
