//===-- FormatClasses.h -----------------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_DATAFORMATTERS_FORMATCLASSES_H
#define LLDB_DATAFORMATTERS_FORMATCLASSES_H

#include <functional>
#include <memory>
#include <string>
#include <vector>

#include "lldb/DataFormatters/TypeFormat.h"
#include "lldb/DataFormatters/TypeSummary.h"
#include "lldb/DataFormatters/TypeSynthetic.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Symbol/Type.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-public.h"

namespace lldb_private {

class HardcodedFormatters {
public:
  template <typename FormatterType>
  using HardcodedFormatterFinder =
      std::function<typename FormatterType::SharedPointer(
          lldb_private::ValueObject &, lldb::DynamicValueType,
          FormatManager &)>;

  template <typename FormatterType>
  using HardcodedFormatterFinders =
      std::vector<HardcodedFormatterFinder<FormatterType>>;

  typedef HardcodedFormatterFinders<TypeFormatImpl> HardcodedFormatFinder;
  typedef HardcodedFormatterFinders<TypeSummaryImpl> HardcodedSummaryFinder;
  typedef HardcodedFormatterFinders<SyntheticChildren> HardcodedSyntheticFinder;
};

class FormattersMatchCandidate {
public:
  // Contains flags to indicate how this candidate was generated (e.g. if
  // typedefs were stripped, or pointers were skipped). These are later compared
  // to flags in formatters to confirm a string match.
  struct Flags {
    bool stripped_pointer = false;
    bool stripped_reference = false;
    bool stripped_typedef = false;

    // Returns a copy of this with the "stripped pointer" flag set.
    Flags WithStrippedPointer() {
      Flags result(*this);
      result.stripped_pointer = true;
      return result;
    }

    // Returns a copy of this with the "stripped reference" flag set.
    Flags WithStrippedReference() {
      Flags result(*this);
      result.stripped_reference = true;
      return result;
    }

    // Returns a copy of this with the "stripped typedef" flag set.
    Flags WithStrippedTypedef() {
      Flags result(*this);
      result.stripped_typedef = true;
      return result;
    }
  };

  FormattersMatchCandidate(ConstString name,
                           ScriptInterpreter *script_interpreter, TypeImpl type,
                           Flags flags, uint32_t ptr_stripped_depth = 0)
      : m_type_name(name), m_script_interpreter(script_interpreter),
        m_type(type), m_flags(flags), m_ptr_stripped_depth(ptr_stripped_depth) {
  }

  ~FormattersMatchCandidate() = default;

  ConstString GetTypeName() const { return m_type_name; }

  TypeImpl GetType() const { return m_type; }

  ScriptInterpreter *GetScriptInterpreter() const {
    return m_script_interpreter;
  }

  bool DidStripPointer() const { return m_flags.stripped_pointer; }

  bool DidStripReference() const { return m_flags.stripped_reference; }

  bool DidStripTypedef() const { return m_flags.stripped_typedef; }

  uint32_t GetPtrStrippedDepth() const { return m_ptr_stripped_depth; }

  template <class Formatter>
  bool IsMatch(const std::shared_ptr<Formatter> &formatter_sp) const {
    if (!formatter_sp)
      return false;
    if (formatter_sp->Cascades() == false && DidStripTypedef())
      return false;
    if (formatter_sp->SkipsPointers() && DidStripPointer())
      return false;
    if (formatter_sp->GetPtrMatchDepth() < GetPtrStrippedDepth())
      return false;
    if (formatter_sp->SkipsReferences() && DidStripReference())
      return false;
    return true;
  }

private:
  ConstString m_type_name;
  // If a formatter provides a matching callback function, we need the script
  // interpreter and the type object (as an argument to the callback).
  ScriptInterpreter *m_script_interpreter;
  TypeImpl m_type;
  Flags m_flags;
  uint32_t m_ptr_stripped_depth;
};

typedef std::vector<FormattersMatchCandidate> FormattersMatchVector;
typedef std::vector<lldb::LanguageType> CandidateLanguagesVector;

class FormattersMatchData {
public:
  FormattersMatchData(ValueObject &, lldb::DynamicValueType);

  FormattersMatchVector GetMatchesVector();

  ConstString GetTypeForCache();

  CandidateLanguagesVector GetCandidateLanguages();

  ValueObject &GetValueObject();

  lldb::DynamicValueType GetDynamicValueType();

private:
  ValueObject &m_valobj;
  lldb::DynamicValueType m_dynamic_value_type;
  std::pair<FormattersMatchVector, bool> m_formatters_match_vector;
  ConstString m_type_for_cache;
  CandidateLanguagesVector m_candidate_languages;
};

class TypeNameSpecifierImpl {
public:
  TypeNameSpecifierImpl() = default;

  TypeNameSpecifierImpl(llvm::StringRef name,
                        lldb::FormatterMatchType match_type)
      : m_match_type(match_type) {
    m_type.m_type_name = std::string(name);
  }

  // if constructing with a given type, we consider that a case of exact match.
  TypeNameSpecifierImpl(lldb::TypeSP type)
      : m_match_type(lldb::eFormatterMatchExact) {
    if (type) {
      m_type.m_type_name = std::string(type->GetName().GetStringRef());
      m_type.m_compiler_type = type->GetForwardCompilerType();
    }
  }

  TypeNameSpecifierImpl(CompilerType type)
      : m_match_type(lldb::eFormatterMatchExact) {
    if (type.IsValid()) {
      m_type.m_type_name.assign(type.GetTypeName().GetCString());
      m_type.m_compiler_type = type;
    }
  }

  const char *GetName() {
    if (m_type.m_type_name.size())
      return m_type.m_type_name.c_str();
    return nullptr;
  }

  CompilerType GetCompilerType() {
    if (m_type.m_compiler_type.IsValid())
      return m_type.m_compiler_type;
    return CompilerType();
  }

  lldb::FormatterMatchType GetMatchType() { return m_match_type; }

  bool IsRegex() { return m_match_type == lldb::eFormatterMatchRegex; }

private:
  lldb::FormatterMatchType m_match_type = lldb::eFormatterMatchExact;
  // TODO: Replace this with TypeAndOrName.
  struct TypeOrName {
    std::string m_type_name;
    CompilerType m_compiler_type;
  };
  TypeOrName m_type;

  TypeNameSpecifierImpl(const TypeNameSpecifierImpl &) = delete;
  const TypeNameSpecifierImpl &
  operator=(const TypeNameSpecifierImpl &) = delete;
};

} // namespace lldb_private

#endif // LLDB_DATAFORMATTERS_FORMATCLASSES_H
