//===-- ConfigTesting.h - Helpers for configuration tests -------*- 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 LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_CONFIGTESTING_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_CONFIGTESTING_H

#include "Protocol.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/SourceMgr.h"
#include "gmock/gmock.h"
#include <functional>
#include <optional>

namespace clang {
namespace clangd {
namespace config {

// Provides a DiagnosticsCallback that records diganostics.
// Unlike just pushing them into a vector, underlying storage need not survive.
struct CapturedDiags {
  std::function<void(const llvm::SMDiagnostic &)> callback() {
    return [this](const llvm::SMDiagnostic &D) {
      if (Files.empty() || Files.back() != D.getFilename())
        Files.push_back(D.getFilename().str());

      if (D.getKind() > llvm::SourceMgr::DK_Warning)
        return;

      Diagnostics.emplace_back();
      Diag &Out = Diagnostics.back();
      Out.Message = D.getMessage().str();
      Out.Kind = D.getKind();
      Out.Pos.line = D.getLineNo() - 1;
      Out.Pos.character = D.getColumnNo(); // Zero-based - bug in SourceMgr?
      if (!D.getRanges().empty()) {
        const auto &R = D.getRanges().front();
        Out.Rng.emplace();
        Out.Rng->start.line = Out.Rng->end.line = Out.Pos.line;
        Out.Rng->start.character = R.first;
        Out.Rng->end.character = R.second;
      }
    };
  }
  struct Diag {
    std::string Message;
    llvm::SourceMgr::DiagKind Kind;
    Position Pos;
    std::optional<Range> Rng;

    friend void PrintTo(const Diag &D, std::ostream *OS) {
      *OS << (D.Kind == llvm::SourceMgr::DK_Error ? "error: " : "warning: ")
          << D.Message << "@" << llvm::to_string(D.Pos);
    }
  };
  std::vector<Diag> Diagnostics;  // Warning or higher.
  std::vector<std::string> Files; // Filename from diagnostics including notes.

  void clear() {
    Diagnostics.clear();
    Files.clear();
  }
};

MATCHER_P(diagMessage, M, "") { return arg.Message == M; }
MATCHER_P(diagKind, K, "") { return arg.Kind == K; }
MATCHER_P(diagPos, P, "") { return arg.Pos == P; }
MATCHER_P(diagRange, R, "") { return arg.Rng == R; }

inline Position toPosition(llvm::SMLoc L, const llvm::SourceMgr &SM) {
  auto LineCol = SM.getLineAndColumn(L);
  Position P;
  P.line = LineCol.first - 1;
  P.character = LineCol.second - 1;
  return P;
}

inline Range toRange(llvm::SMRange R, const llvm::SourceMgr &SM) {
  return Range{toPosition(R.Start, SM), toPosition(R.End, SM)};
}

} // namespace config
} // namespace clangd
} // namespace clang

#endif
