// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2020-2024 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: Maria Teguiani
// Author: Giuliano Procida
// Author: Ignes Simeonova

#ifndef STG_COMPARISON_H_
#define STG_COMPARISON_H_

#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>

#include "graph.h"
#include "runtime.h"

namespace stg {
namespace diff {

struct Ignore {
  enum Value {
    // noise reduction
    SYMBOL_TYPE_PRESENCE,
    TYPE_DECLARATION_STATUS,
    PRIMITIVE_TYPE_ENCODING,
    MEMBER_SIZE,
    ENUM_UNDERLYING_TYPE,
    QUALIFIER,
    SYMBOL_CRC,
    // ABI compatibility testing
    INTERFACE_ADDITION,
    TYPE_DEFINITION_ADDITION,
  };

  using Bitset = uint16_t;

  Ignore() = default;
  template <typename... Values>
  explicit Ignore(Values... values) {
    for (auto value : {values...}) {
      Set(value);
    }
  }

  void Set(Value other) {
    bitset = bitset | (1 << other);
  }
  bool Test(Value other) const {
    return (bitset & (1 << other)) != 0;
  }

  Bitset bitset = 0;
};

std::optional<Ignore::Value> ParseIgnore(std::string_view ignore);

struct IgnoreUsage {};
std::ostream& operator<<(std::ostream& os, IgnoreUsage);

using Comparison = std::pair<std::optional<Id>, std::optional<Id>>;

struct DiffDetail {
  DiffDetail(const std::string& text, const Comparison& edge)
      : text(text), edge(edge) {}
  std::string text;
  Comparison edge;
};

struct Diff {
  // This diff node corresponds to an entity that is reportable, if it or any of
  // its children (excluding reportable ones) has changed.
  bool holds_changes = false;
  // This diff node contains a local (non-recursive) change.
  bool has_changes = false;
  std::vector<DiffDetail> details;

  void Add(const std::string& text, const Comparison& comparison) {
    details.emplace_back(text, comparison);
  }
};

struct HashComparison {
  size_t operator()(const Comparison& comparison) const {
    size_t seed = 0;
    const std::hash<std::optional<Id>> h;
    combine_hash(seed, h(comparison.first));
    combine_hash(seed, h(comparison.second));
    return seed;
  }
  static void combine_hash(size_t& seed, size_t hash) {
    seed ^= hash + 0x9e3779b97f4a7c15 + (seed << 12) + (seed >> 4);
  }
};

using Outcomes = std::unordered_map<Comparison, Diff, HashComparison>;

std::pair<Id, std::vector<std::string>> ResolveTypedefs(
    const Graph& graph, Id id);

Comparison Compare(Runtime& runtime, Ignore ignore, const Graph& graph,
                   Id root1, Id root2, Outcomes& outcomes);

}  // namespace diff
}  // namespace stg

#endif  // STG_COMPARISON_H_
