//===--- IndexerQuery.h - A set of indexer query interfaces ---------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines the base indexer queries that can be used with
// refactoring continuations.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLING_REFACTOR_INDEXER_QUERY_H
#define LLVM_CLANG_TOOLING_REFACTOR_INDEXER_QUERY_H

#include "clang/Tooling/Refactor/RefactoringOperationState.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include <vector>

namespace clang {
namespace tooling {
namespace indexer {

/// Represents an abstract indexer query.
class IndexerQuery {
public:
  const char *BaseUID;
  const char *NameUID;

  IndexerQuery(const char *BaseUID, const char *NameUID)
      : BaseUID(BaseUID), NameUID(NameUID) {}
  virtual ~IndexerQuery() {}

  virtual void invalidateTUSpecificState() = 0;

  /// Checks if this query was satisfied. Returns true if it wasn't and reports
  /// appropriate errors.
  virtual bool verify(ASTContext &) { return false; }

  // Mainly used for testing.
  static llvm::Error loadResultsFromYAML(StringRef Source,
                                         ArrayRef<IndexerQuery *> Queries);

  static bool classof(const IndexerQuery *) { return true; }
};

/// An abstract AST query that can produce an AST unit in which the refactoring
/// continuation will run.
class ASTProducerQuery : public IndexerQuery {
  static const char *BaseUIDString;

public:
  /// Deriving AST producer queries can redefine this type to generate custom
  /// results that are then passed into the refactoring continuations.
  using ResultTy = void;

  ASTProducerQuery(const char *NameUID)
      : IndexerQuery(BaseUIDString, NameUID) {}

  static bool classof(const IndexerQuery *Q) {
    return Q->BaseUID == BaseUIDString;
  }
};

/// A query that finds a file that contains/should contain the implementation of
/// some declaration.
class ASTUnitForImplementationOfDeclarationQuery final
    : public ASTProducerQuery {
  static const char *NameUIDString;

  const Decl *D;
  PersistentFileID Result;

public:
  ASTUnitForImplementationOfDeclarationQuery(const Decl *D)
      : ASTProducerQuery(NameUIDString), D(D), Result("") {}

  using ResultTy = FileID;

  const Decl *getDecl() const { return D; }

  void invalidateTUSpecificState() override { D = nullptr; }

  void setResult(PersistentFileID File) { Result = std::move(File); }

  bool verify(ASTContext &Context) override;

  const PersistentFileID &getResult() const { return Result; }

  static bool classof(const IndexerQuery *D) {
    return D->NameUID == NameUIDString;
  }
};

/// Returns an indexer query that will allow a refactoring continuation to run
/// in an AST unit that contains a file that should contain the implementation
/// of the given declaration \p D.
///
/// The continuation function will receive \c FileID that corresponds to the
/// implementation file. The indexer can decide which file should be used as an
/// implementation of a declaration based on a number of different heuristics.
/// It does not guarantee that the file will actually have any declarations that
/// correspond to the implementation of \p D yet, as the indexer may decide to
/// point to a file that it thinks will have the implementation declarations in
/// the future.
std::unique_ptr<ASTUnitForImplementationOfDeclarationQuery>
fileThatShouldContainImplementationOf(const Decl *D);

/// A declaration predicate operates.
struct DeclPredicate {
  const char *Name;

  DeclPredicate(const char *Name) : Name(Name) {}

  bool operator==(const DeclPredicate &P) const {
    return StringRef(Name) == P.Name;
  }
  bool operator!=(const DeclPredicate &P) const {
    return StringRef(Name) != P.Name;
  }
};

/// Represents a declaration predicate that will evaluate to either 'true' or
/// 'false' in an indexer query.
struct BoolDeclPredicate {
  DeclPredicate Predicate;
  bool IsInverted;

  BoolDeclPredicate(DeclPredicate Predicate, bool IsInverted = false)
      : Predicate(Predicate), IsInverted(IsInverted) {}

  BoolDeclPredicate operator!() const {
    return BoolDeclPredicate(Predicate, /*IsInverted=*/!IsInverted);
  }
};

namespace detail {

/// AST-like representation for decl predicates.
class DeclPredicateNode {
public:
  const char *NameUID;
  DeclPredicateNode(const char *NameUID) : NameUID(NameUID) {}

  virtual ~DeclPredicateNode() { }

  static std::unique_ptr<DeclPredicateNode>
  create(const DeclPredicate &Predicate);
  static std::unique_ptr<DeclPredicateNode>
  create(const BoolDeclPredicate &Predicate);

  static bool classof(const DeclPredicateNode *) { return true; }
};

class DeclPredicateNodePredicate : public DeclPredicateNode {
  static const char *NameUIDString;

  DeclPredicate Predicate;

public:
  DeclPredicateNodePredicate(const DeclPredicate &Predicate)
      : DeclPredicateNode(NameUIDString), Predicate(Predicate) {}

  const DeclPredicate &getPredicate() const { return Predicate; }

  static bool classof(const DeclPredicateNode *P) {
    return P->NameUID == NameUIDString;
  }
};

class DeclPredicateNotPredicate : public DeclPredicateNode {
  static const char *NameUIDString;

  std::unique_ptr<DeclPredicateNode> Child;

public:
  DeclPredicateNotPredicate(std::unique_ptr<DeclPredicateNode> Child)
      : DeclPredicateNode(NameUIDString), Child(std::move(Child)) {}

  const DeclPredicateNode &getChild() const { return *Child; }

  static bool classof(const DeclPredicateNode *P) {
    return P->NameUID == NameUIDString;
  }
};

} // end namespace detail

enum class QueryBoolResult {
  Unknown,
  Yes,
  No,
};

// FIXME: Check that 'T' is either a PersistentDeclRef<> or a Decl *.
template <typename T> struct Indexed {
  T Decl;
  // FIXME: Generalize better in the new refactoring engine.
  QueryBoolResult IsNotDefined;

  Indexed(T Decl, QueryBoolResult IsNotDefined = QueryBoolResult::Unknown)
      : Decl(Decl), IsNotDefined(IsNotDefined) {}

  Indexed(Indexed<T> &&Other) = default;
  Indexed &operator=(Indexed<T> &&Other) = default;
  Indexed(const Indexed<T> &Other) = default;
  Indexed &operator=(const Indexed<T> &Other) = default;

  /// True iff the declaration is not defined in the entire project.
  bool isNotDefined() const {
    // FIXME: This is hack. Need a better system in the new engine.
    return IsNotDefined == QueryBoolResult::Yes;
  }
};

/// Transforms one set of declarations into another using some predicate.
class DeclarationsQuery : public IndexerQuery {
  static const char *BaseUIDString;

  std::vector<const Decl *> Input;
  std::unique_ptr<detail::DeclPredicateNode> Predicate;

protected:
  std::vector<Indexed<PersistentDeclRef<Decl>>> Output;

public:
  DeclarationsQuery(std::vector<const Decl *> Input,
                    std::unique_ptr<detail::DeclPredicateNode> Predicate)
      : IndexerQuery(BaseUIDString, nullptr), Input(std::move(Input)),
        Predicate(std::move(Predicate)) {
    assert(!this->Input.empty() && "empty declarations list!");
  }

  ArrayRef<const Decl *> getInputs() const { return Input; }

  void invalidateTUSpecificState() override { Input.clear(); }

  bool verify(ASTContext &Context) override;

  void setOutput(std::vector<Indexed<PersistentDeclRef<Decl>>> Output) {
    this->Output = Output;
  }

  const detail::DeclPredicateNode &getPredicateNode() const {
    return *Predicate;
  }

  static bool classof(const IndexerQuery *Q) {
    return Q->BaseUID == BaseUIDString;
  }
};

/// The \c DeclEntity class acts as a proxy for the entity that represents a
/// declaration in the indexer. It defines a set of declaration predicates that
/// can be used in indexer queries.
struct DeclEntity {
  /// The indexer will evaluate this predicate to 'true' when a certain
  /// declaration has a corresponding definition.
  BoolDeclPredicate isDefined() const {
    return BoolDeclPredicate("decl.isDefined");
  }
};

template <typename T>
class ManyToManyDeclarationsQuery final
    : public std::enable_if<std::is_base_of<Decl, T>::value,
                            DeclarationsQuery>::type {
public:
  ManyToManyDeclarationsQuery(
      ArrayRef<const T *> Input,
      std::unique_ptr<detail::DeclPredicateNode> Predicate)
      : DeclarationsQuery(std::vector<const Decl *>(Input.begin(), Input.end()),
                          std::move(Predicate)) {}

  std::vector<Indexed<PersistentDeclRef<T>>> getOutput() const {
    std::vector<Indexed<PersistentDeclRef<T>>> Results;
    for (const auto &Ref : DeclarationsQuery::Output)
      Results.push_back(Indexed<PersistentDeclRef<T>>(
          PersistentDeclRef<T>(Ref.Decl.USR), Ref.IsNotDefined));
    return Results;
  }
};

/// Returns an indexer query that will pass a filtered list of declarations to
/// a refactoring continuation.
///
/// The filtering is done based on predicates that are available on the \c
/// DeclEntity types. For example, you can use the following invocation to
/// find a set of declarations that are defined in the entire project:
///
/// \code
/// filter({ MyDeclA, MyDeclB }, [] (const DeclEntity &D) { return D.isDefined()
/// })
/// \endcode
template <typename T>
std::unique_ptr<ManyToManyDeclarationsQuery<T>>
filter(ArrayRef<const T *> Declarations,
       BoolDeclPredicate (*Fn)(const DeclEntity &),
       typename std::enable_if<std::is_base_of<Decl, T>::value>::type * =
           nullptr) {
  return std::make_unique<ManyToManyDeclarationsQuery<T>>(
      Declarations, detail::DeclPredicateNode::create(Fn(DeclEntity())));
}

} // end namespace indexer
} // end namespace tooling
} // end namespace clang

#endif // LLVM_CLANG_TOOLING_REFACTOR_REFACTORING_INDEXER_QUERY_H
