//===--- IndexerQueries.cpp - Indexer queries -----------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h"
#include "clang/Tooling/Refactor/IndexerQuery.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/YAMLTraits.h"

using namespace clang;
using namespace clang::tooling;
using namespace clang::tooling::indexer;
using namespace clang::tooling::indexer::detail;
using namespace llvm::yaml;

const char *ASTProducerQuery::BaseUIDString = "ast.producer.query";
const char *DeclarationsQuery::BaseUIDString = "decl.query";
const char *ASTUnitForImplementationOfDeclarationQuery::NameUIDString =
    "file.for.impl.of.decl";

const char *DeclPredicateNodePredicate::NameUIDString = "decl.predicate";
const char *DeclPredicateNotPredicate::NameUIDString = "not.decl.predicate";

std::unique_ptr<DeclPredicateNode>
DeclPredicateNode::create(const DeclPredicate &Predicate) {
  return llvm::make_unique<DeclPredicateNodePredicate>(Predicate);
}

std::unique_ptr<DeclPredicateNode>
DeclPredicateNode::create(const BoolDeclPredicate &Predicate) {
  if (Predicate.IsInverted)
    return llvm::make_unique<DeclPredicateNotPredicate>(
        create(Predicate.Predicate));
  return create(Predicate.Predicate);
}

std::unique_ptr<ASTUnitForImplementationOfDeclarationQuery>
clang::tooling::indexer::fileThatShouldContainImplementationOf(const Decl *D) {
  return llvm::make_unique<ASTUnitForImplementationOfDeclarationQuery>(D);
}

bool ASTUnitForImplementationOfDeclarationQuery::verify(ASTContext &Context) {
  if (!D) {
    assert(false && "Query should be verified before persisting");
    return false;
  }
  // Check if we've got the filename.
  if (!Result.Filename.empty())
    return false;
  Context.getDiagnostics().Report(
      D->getLocation(), diag::err_ref_continuation_missing_implementation)
      << isa<ObjCContainerDecl>(D) << cast<NamedDecl>(D);
  return true;
}

bool DeclarationsQuery::verify(ASTContext &Context) {
  if (Input.empty()) {
    assert(false && "Query should be verified before persisting");
    return false;
  }
  if (!Output.empty()) {
    // At least one output declaration must be valid.
    for (const auto &Ref : Output) {
      if (!Ref.Decl.USR.empty())
        return false;
    }
  }
  // FIXME: This is too specific, the new refactoring engine at llvm.org should
  // generalize this.
  Context.getDiagnostics().Report(
      Input[0]->getLocation(),
      diag::err_implement_declared_methods_all_implemented);
  return true;
}

namespace {

struct QueryPredicateNode {
  std::string Name;
  std::vector<int> IntegerValues;
};

struct QueryYAMLNode {
  std::string Name;
  std::vector<QueryPredicateNode> PredicateResults;
  std::string FilenameResult;
};

} // end anonymous namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(QueryPredicateNode)
LLVM_YAML_IS_SEQUENCE_VECTOR(QueryYAMLNode)

namespace llvm {
namespace yaml {

template <> struct MappingTraits<QueryPredicateNode> {
  static void mapping(IO &Yaml, QueryPredicateNode &Predicate) {
    Yaml.mapRequired("name", Predicate.Name);
    Yaml.mapRequired("intValues", Predicate.IntegerValues);
  }
};

template <> struct MappingTraits<QueryYAMLNode> {
  static void mapping(IO &Yaml, QueryYAMLNode &Query) {
    Yaml.mapRequired("name", Query.Name);
    Yaml.mapOptional("predicateResults", Query.PredicateResults);
    Yaml.mapOptional("filenameResult", Query.FilenameResult);
    // FIXME: Report an error if no results are provided at all.
  }
};

} // end namespace yaml
} // end namespace llvm

llvm::Error
IndexerQuery::loadResultsFromYAML(StringRef Source,
                                  ArrayRef<IndexerQuery *> Queries) {
  std::vector<QueryYAMLNode> QueryResults;
  Input YamlIn(Source);
  YamlIn >> QueryResults;
  if (YamlIn.error())
    return llvm::make_error<llvm::StringError>("Failed to parse query results",
                                               YamlIn.error());
  if (QueryResults.size() != Queries.size())
    return llvm::make_error<llvm::StringError>("Mismatch in query results size",
                                               llvm::errc::invalid_argument);
  for (const auto &QueryTuple : llvm::zip(Queries, QueryResults)) {
    IndexerQuery *Query = std::get<0>(QueryTuple);
    const QueryYAMLNode &Result = std::get<1>(QueryTuple);
    if ((Query->NameUID && Query->NameUID != Result.Name) &&
        (Query->BaseUID && Query->BaseUID != Result.Name))
      continue;
    if (auto *DQ = dyn_cast<DeclarationsQuery>(Query)) {
      const DeclPredicateNode &Predicate = DQ->getPredicateNode();
      DeclPredicate ActualPredicate("");
      bool IsNot = false;
      if (const auto *Not = dyn_cast<DeclPredicateNotPredicate>(&Predicate)) {
        ActualPredicate =
            cast<DeclPredicateNodePredicate>(Not->getChild()).getPredicate();
        IsNot = true;
      } else
        ActualPredicate =
            cast<DeclPredicateNodePredicate>(Predicate).getPredicate();
      for (const auto &PredicateResult : Result.PredicateResults) {
        if (PredicateResult.Name != ActualPredicate.Name)
          continue;
        std::vector<Indexed<PersistentDeclRef<Decl>>> Output;
        for (const auto &ResultTuple :
             zip(DQ->getInputs(), PredicateResult.IntegerValues)) {
          const Decl *D = std::get<0>(ResultTuple);
          int Result = std::get<1>(ResultTuple);
          bool Value = (IsNot ? !Result : !!Result);
          Output.push_back(Indexed<PersistentDeclRef<Decl>>(
              PersistentDeclRef<Decl>::create(Value ? D : nullptr),
              Value ? QueryBoolResult::Yes : QueryBoolResult::No));
        }
        DQ->setOutput(std::move(Output));
        break;
      }
    } else if (auto *AQ =
                   dyn_cast<ASTUnitForImplementationOfDeclarationQuery>(Query))
      AQ->setResult(Result.FilenameResult);
  }
  return llvm::Error::success();
}
