// Copyright (C) 2018 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 ABI_DIFF_HELPERS_H_
#define ABI_DIFF_HELPERS_H_

#include "repr/ir_diff_dumper.h"
#include "repr/ir_diff_representation.h"
#include "repr/ir_representation.h"

#include <deque>
#include <set>


namespace header_checker {
namespace repr {


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

class DiffStatus {
 public:
  enum Status {
    kNoDiff = 0,
    // The diff has been added to the IRDiffDumper.
    kIndirectDiff = 1,
    // The diff has not been added to the IRDiffDumper, and the new ABI is
    // an extension to the old ABI.
    kDirectExt = 2,
    // The diff has not been added to the IRDiffDumper.
    kDirectDiff = 3,
  };

  // Allow implicit conversion.
  DiffStatus(Status status) : status_(status) {}

  bool HasDiff() const { return status_ != kNoDiff; }

  bool IsDirectDiff() const {
    return status_ == kDirectDiff || status_ == kDirectExt;
  }

  bool IsExtension() const { return status_ == kDirectExt; }

  DiffStatus &CombineWith(DiffStatus other) {
    status_ = std::max(status_, other.status_);
    return *this;
  }

 private:
  Status status_;
};

struct RecordFieldDiffResult {
  DiffStatus status = DiffStatus::kNoDiff;
  std::vector<RecordFieldDiffIR> diffed_fields;
  std::vector<const RecordFieldIR *> removed_fields;
  std::vector<const RecordFieldIR *> added_fields;
};

class TypeStackGuard {
 public:
  TypeStackGuard(std::deque<std::string> &type_stack,
                 const std::string &type_name)
      : type_stack_(type_stack) {
    type_stack_.push_back(type_name);
  }

  ~TypeStackGuard() { type_stack_.pop_back(); }

 private:
  std::deque<std::string> &type_stack_;
};

struct DiffPolicyOptions {
  DiffPolicyOptions(bool consider_opaque_types_different)
      : consider_opaque_types_different_(consider_opaque_types_different) {}

  bool consider_opaque_types_different_;
};

class AbiDiffHelper {
 public:
  AbiDiffHelper(
      const AbiElementMap<const TypeIR *> &old_types,
      const AbiElementMap<const TypeIR *> &new_types,
      const DiffPolicyOptions &diff_policy_options,
      std::set<std::string> *type_cache,
      const std::set<std::string> &ignored_linker_set_keys,
      IRDiffDumper *ir_diff_dumper = nullptr)
      : old_types_(old_types), new_types_(new_types),
        diff_policy_options_(diff_policy_options), type_cache_(type_cache),
        ignored_linker_set_keys_(ignored_linker_set_keys),
        ir_diff_dumper_(ir_diff_dumper) {}

  // Concatenate the strings in type_stack.
  std::string UnwindTypeStack();

  bool AreOpaqueTypesEqual(const std::string &old_type_str,
                           const std::string &new_type_str) const;

  DiffStatus CompareAndDumpTypeDiff(
      const std::string &old_type_str, const std::string &new_type_str,
      IRDiffDumper::DiffKind diff_kind = DiffMessageIR::Unreferenced);

  DiffStatus CompareAndDumpTypeDiff(
      const TypeIR *old_type, const TypeIR *new_type, LinkableMessageKind kind,
      IRDiffDumper::DiffKind diff_kind = DiffMessageIR::Unreferenced);

  DiffStatus CompareRecordTypes(const RecordTypeIR *old_type,
                                const RecordTypeIR *new_type,
                                IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareEnumTypes(const EnumTypeIR *old_type,
                              const EnumTypeIR *new_type,
                              IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareFunctionTypes(const CFunctionLikeIR *old_type,
                                  const CFunctionLikeIR *new_type,
                                  DiffMessageIR::DiffKind diff_kind);

  DiffStatus CompareTemplateInfo(
      const std::vector<TemplateElementIR> &old_template_elements,
      const std::vector<TemplateElementIR> &new_template_elements,
      IRDiffDumper::DiffKind diff_kind);

 private:
  DiffStatus CompareQualifiedTypes(const QualifiedTypeIR *old_type,
                                   const QualifiedTypeIR *new_type,
                                   IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareArrayTypes(const ArrayTypeIR *old_type,
                               const ArrayTypeIR *new_type,
                               IRDiffDumper::DiffKind diff_kind);

  DiffStatus ComparePointerTypes(const PointerTypeIR *old_type,
                                 const PointerTypeIR *new_type,
                                 IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareLvalueReferenceTypes(const LvalueReferenceTypeIR *old_type,
                                         const LvalueReferenceTypeIR *new_type,
                                         IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareRvalueReferenceTypes(const RvalueReferenceTypeIR *old_type,
                                         const RvalueReferenceTypeIR *new_type,
                                         IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareBuiltinTypes(const BuiltinTypeIR *old_type,
                                 const BuiltinTypeIR *new_type);

  static void CompareEnumFields(
      const std::vector<EnumFieldIR> &old_fields,
      const std::vector<EnumFieldIR> &new_fields,
      EnumTypeDiffIR *enum_type_diff_ir);


  void ReplaceRemovedFieldTypeIdsWithTypeNames(
      std::vector<RecordFieldIR *> *removed_fields);

  void ReplaceDiffedFieldTypeIdsWithTypeNames(
      RecordFieldDiffIR *diffed_field);

  std::vector<std::pair<RecordFieldIR, RecordFieldIR>>
  FixupDiffedFieldTypeIds(
      const std::vector<RecordFieldDiffIR> &field_diffs);

  DiffStatus CompareAccess(AccessSpecifierIR old_access,
                           AccessSpecifierIR new_access);

  DiffStatus CompareCommonRecordFields(const RecordFieldIR *old_field,
                                       const RecordFieldIR *new_field,
                                       IRDiffDumper::DiffKind diff_kind);

  DiffStatus FilterOutRenamedRecordFields(
      DiffMessageIR::DiffKind diff_kind,
      std::vector<const RecordFieldIR *> &old_fields,
      std::vector<const RecordFieldIR *> &new_fields);

  RecordFieldDiffResult CompareRecordFields(
      const std::vector<RecordFieldIR> &old_fields,
      const std::vector<RecordFieldIR> &new_fields,
      IRDiffDumper::DiffKind diff_kind);

  bool CompareBaseSpecifiers(
      const std::vector<CXXBaseSpecifierIR> &old_base_specifiers,
      const std::vector<CXXBaseSpecifierIR> &new_base_specifiers,
      IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareFunctionParameters(
      const std::vector<ParamIR> &old_parameters,
      const std::vector<ParamIR> &new_parameters,
      IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareParameterTypes(const std::string &old_type_id,
                                   const std::string &new_type_id,
                                   IRDiffDumper::DiffKind diff_kind);

  DiffStatus CompareReturnTypes(const std::string &old_type_id,
                                const std::string &new_type_id,
                                IRDiffDumper::DiffKind diff_kind);

 protected:
  const AbiElementMap<const TypeIR *> &old_types_;
  const AbiElementMap<const TypeIR *> &new_types_;
  const DiffPolicyOptions &diff_policy_options_;
  std::set<std::string> *type_cache_;
  std::deque<std::string> type_stack_;
  const std::set<std::string> &ignored_linker_set_keys_;
  IRDiffDumper *ir_diff_dumper_;
};

void ReplaceTypeIdsWithTypeNames(
    const AbiElementMap<const TypeIR *> &type_graph, LinkableMessageIR *lm);


}  // namespace repr
}  // namespace header_checker


#endif  // ABI_DIFF_HELPERS_H_
